Compare commits

...
Author SHA1 Message Date
StandardNotes CI 0d8a05b805 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].5
2023-01-09 07:40:10 +00:00
Karol Sójko 10751ea2ed feat(snjs): add getting recovery codes (#2132) 2023-01-09 08:13:12 +01:00
StandardNotes CI 6b774db222 chore(release): publish
- @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].4
2023-01-09 06:23:12 +00:00
Karol SójkoandMo be028ff87b feat(snjs): add sign in with recovery codes use case (#2130)
* feat(snjs): add sign in with recovery codes use case

* fix(snjs): code review adjustments

* fix(snjs): remove unnecessary exposed getter

* fix(services): waiting for event handling

* fix: preferences test

Co-authored-by: Mo <[email protected]>
2023-01-09 06:52:56 +01:00
StandardNotes CI 5f09fc74da chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2023-01-07 09:52:22 +00:00
Aman Harwara 340f4f7f09 fix: Fixed issue where search button would not show up in Files view 2023-01-07 14:50:06 +05:30
Aman Harwara 371a47d838 refactor: new note default editor options 2023-01-07 14:47:17 +05:30
StandardNotes CI b839ebb58a chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2023-01-06 20:16:01 +00:00
Aman Harwara 482c4d1ca4 fix: Fixed issue where the "Attached to" in table view would report an extra number of links 2023-01-07 01:18:50 +05:30
StandardNotes CI 5f96281948 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].1
2023-01-06 16:02:46 +00:00
Aman Harwara 2e79fc412c fix: Fixed issue where the importer would not correctly parse Google Keep JSON notes 2023-01-06 20:57:18 +05:30
StandardNotes CI 5bdbfe9fc7 chore(release): publish
- @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].0
2023-01-06 11:59:51 +00:00
Aman Harwara dd8ccdeadc feat: Added per-tag preference to use table layout and removed "Files Table View" from Labs 2023-01-06 17:00:19 +05:30
StandardNotes CI eea97362f8 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
2023-01-05 20:59:57 +00:00
Aman Harwara 01294868c9 Merge branch 'main' of github.com:standardnotes/app 2023-01-06 02:02:16 +05:30
Aman Harwara 77ec9ca335 refactor: files table view -> content table view 2023-01-06 02:02:07 +05:30
StandardNotes CI 3e6c752967 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
2023-01-05 18:43:40 +00:00
Aman Harwara ba017d42f5 feat: Added keyboard navigation to Files table view (#2131) 2023-01-05 23:45:10 +05:30
StandardNotes CI bd3df71dd4 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2023-01-05 10:28:19 +00:00
Aman Harwara 22c6e2c56c feat: Added linked items container & autocomplete input to file preview modal 2023-01-05 15:32:18 +05:30
StandardNotes CI 496525ef61 chore(release): publish
- @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].0
2023-01-04 20:27:27 +00:00
Aman Harwara 6b2f8f3da2 Merge branch 'main' of github.com:standardnotes/app 2023-01-05 01:23:24 +05:30
Aman Harwara e57b28fe29 feat: Added file options menu to file preview modal 2023-01-05 01:23:15 +05:30
Aman Harwara ba4b6a580b feat: Added "Add tag" option to file context menu 2023-01-05 01:16:35 +05:30
Mo 59fc68296b refactor: optimize delay between batches on mobile to allow UI interactivity during load (#2129) 2023-01-04 13:31:45 -06:00
StandardNotes CI 69b2af7612 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].3
2023-01-04 19:10:43 +00:00
Aman Harwara 30dda73e90 fix: Fixed issue where file upload toast would close before completion if you switched windows 2023-01-05 00:13:42 +05:30
StandardNotes CI 81ea9e896c chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2023-01-04 01:14:43 +00:00
Mo bc9247df41 chore: trigger build 2023-01-03 18:46:49 -06:00
StandardNotes CI 71e8645ca4 chore(release): publish
- @standardnotes/[email protected].1
 - @standardnotes/[email protected]
2023-01-03 23:51:59 +00:00
Mo 72f384c215 ci: remove mobile dev 2023-01-03 17:26:29 -06:00
Mo b65e8562f2 ci: remove unused workflow 2023-01-03 16:30:16 -06:00
138 changed files with 2493 additions and 572 deletions
-96
View File
@@ -1,96 +0,0 @@
name: Mobile TestFlight
on:
push:
tags:
- '*standardnotes/web*'
workflow_dispatch:
jobs:
android:
defaults:
run:
working-directory: packages/mobile
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Export version from package.json
run: |
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
- name: Setup react-native kernel and increase watchers
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
- name: Decode Dev Android keystore
run: |
echo "${{ secrets.DEV_KEYSTORE }}" > keystore.keystore.asc
gpg -d --passphrase "${{ secrets.DEV_KEYSTORE_PASSPHRASE }}" --batch keystore.keystore.asc > android/app/keystore.keystore
- name: Ruby Setup for Fastlane
uses: ruby/setup-ruby@v1
- name: Install dependencies
run: yarn install --immutable
- run: yarn build:mobile
- name: fastlane
uses: maierj/[email protected]
env:
PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }}
BUILD_NUMBER: ${{ github.run_number }}
ANDROID_KEYSTORE_ALIAS: ${{ secrets.DEV_ANDROID_KEYSTORE_ALIAS }}
ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD: ${{ secrets.DEV_ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.DEV_ANDROID_KEYSTORE_PASSWORD }}
GOOGLE_PLAY_JSON_KEY_DATA: ${{ secrets.GOOGLE_PLAY_JSON_KEY_DATA }}
with:
lane: 'android dev'
subdirectory: 'packages/mobile'
ios:
env:
ITMSTRANSPORTER_FORCE_ITMS_PACKAGE_UPLOAD: true
defaults:
run:
working-directory: packages/mobile
runs-on: macos-11
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Export version from package.json
run: |
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
- name: Ruby Setup for Fastlane
uses: ruby/setup-ruby@v1
- name: Install dependencies
run: yarn install --immutable && yarn install:pods
- run: yarn build:mobile
- name: Set ssh connection to Github
uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Export Apple AppStore Connect API key
run: echo "${{ secrets.APPSTORE_CONNECT_KEY }}" > ios/Authkey.p8
- name: Setup fastlane
uses: maierj/[email protected]
env:
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
with:
lane: 'ios setup'
subdirectory: 'packages/mobile'
- name: fastlane
uses: maierj/[email protected]
env:
PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }}
APPLE_APP_ID: ${{ secrets.DEV_APP_APPLE_ID }}
APPSTORE_CONNECT_KEY_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ID }}
APPSTORE_CONNECT_KEY_ISSUER_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ISSUER_ID }}
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
FASTLANE_SESSION: ${{ secrets.FASTLANE_SESSION }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
MATCH_CERTIFICATES_URL: ${{ secrets.MATCH_CERTIFICATES_URL }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
BUILD_NUMBER: ${{ github.run_number }}
with:
lane: 'ios dev'
subdirectory: 'packages/mobile'
+1 -1
View File
@@ -27,7 +27,7 @@
"build:services": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/services run build",
"build:api": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/api run build",
"start:server:web": "lerna run start --scope=@standardnotes/web",
"start:server:e2e": "lerna run start:test-server --scope=@standardnotes/snjs",
"e2e": "lerna run start:test-server --scope=@standardnotes/snjs",
"reset": "find . -type dir -name node_modules | xargs rm -rf && rm -rf yarn.lock && yarn install",
"release:prod": "lerna version --conventional-commits --yes -m \"chore(release): publish\"",
"publish:prod": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.22.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
### Features
* **snjs:** add sign in with recovery codes use case ([#2130](https://github.com/standardnotes/app/issues/2130)) ([be028ff](https://github.com/standardnotes/app/commit/be028ff87bb5306fc266b70567cbea8a13547e7e))
## [1.21.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/api
## [1.21.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/api
## [1.21.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
**Note:** Version bump only for package @standardnotes/api
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.21.2",
"version": "1.22.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -0,0 +1,5 @@
export enum AuthApiOperations {
GenerateRecoveryCodes,
GetRecoveryKeyParams,
SignInWithRecoveryCodes,
}
@@ -0,0 +1,89 @@
import { ApiVersion } from '../../Api'
import { ApiCallError } from '../../Error/ApiCallError'
import { ErrorMessage } from '../../Error/ErrorMessage'
import {
GenerateRecoveryCodesResponse,
RecoveryKeyParamsResponse,
SignInWithRecoveryCodesResponse,
} from '../../Response'
import { AuthServerInterface } from '../../Server'
import { AuthApiOperations } from './AuthApiOperations'
import { AuthApiServiceInterface } from './AuthApiServiceInterface'
export class AuthApiService implements AuthApiServiceInterface {
private operationsInProgress: Map<AuthApiOperations, boolean>
constructor(private authServer: AuthServerInterface) {
this.operationsInProgress = new Map()
}
async generateRecoveryCodes(): Promise<GenerateRecoveryCodesResponse> {
if (this.operationsInProgress.get(AuthApiOperations.GenerateRecoveryCodes)) {
throw new ApiCallError(ErrorMessage.GenericInProgress)
}
this.operationsInProgress.set(AuthApiOperations.GenerateRecoveryCodes, true)
try {
const response = await this.authServer.generateRecoveryCodes()
return response
} catch (error) {
throw new ApiCallError(ErrorMessage.GenericFail)
} finally {
this.operationsInProgress.set(AuthApiOperations.GenerateRecoveryCodes, false)
}
}
async recoveryKeyParams(dto: {
username: string
codeChallenge: string
recoveryCodes: string
}): Promise<RecoveryKeyParamsResponse> {
if (this.operationsInProgress.get(AuthApiOperations.GetRecoveryKeyParams)) {
throw new ApiCallError(ErrorMessage.GenericInProgress)
}
this.operationsInProgress.set(AuthApiOperations.GetRecoveryKeyParams, true)
try {
const response = await this.authServer.recoveryKeyParams({
apiVersion: ApiVersion.v0,
...dto,
})
return response
} catch (error) {
throw new ApiCallError(ErrorMessage.GenericFail)
} finally {
this.operationsInProgress.set(AuthApiOperations.GetRecoveryKeyParams, false)
}
}
async signInWithRecoveryCodes(dto: {
username: string
password: string
codeVerifier: string
recoveryCodes: string
}): Promise<SignInWithRecoveryCodesResponse> {
if (this.operationsInProgress.get(AuthApiOperations.SignInWithRecoveryCodes)) {
throw new ApiCallError(ErrorMessage.GenericInProgress)
}
this.operationsInProgress.set(AuthApiOperations.SignInWithRecoveryCodes, true)
try {
const response = await this.authServer.signInWithRecoveryCodes({
apiVersion: ApiVersion.v0,
...dto,
})
return response
} catch (error) {
throw new ApiCallError(ErrorMessage.GenericFail)
} finally {
this.operationsInProgress.set(AuthApiOperations.SignInWithRecoveryCodes, false)
}
}
}
@@ -0,0 +1,20 @@
import {
GenerateRecoveryCodesResponse,
RecoveryKeyParamsResponse,
SignInWithRecoveryCodesResponse,
} from '../../Response'
export interface AuthApiServiceInterface {
generateRecoveryCodes(): Promise<GenerateRecoveryCodesResponse>
recoveryKeyParams(dto: {
username: string
codeChallenge: string
recoveryCodes: string
}): Promise<RecoveryKeyParamsResponse>
signInWithRecoveryCodes(dto: {
username: string
password: string
codeVerifier: string
recoveryCodes: string
}): Promise<SignInWithRecoveryCodesResponse>
}
+3
View File
@@ -1,3 +1,6 @@
export * from './Auth/AuthApiOperations'
export * from './Auth/AuthApiService'
export * from './Auth/AuthApiServiceInterface'
export * from './Authenticator/AuthenticatorApiOperations'
export * from './Authenticator/AuthenticatorApiService'
export * from './Authenticator/AuthenticatorApiServiceInterface'
@@ -1 +1 @@
export type HttpRequestParams = Record<string, unknown>
export type HttpRequestParams = unknown
+2 -2
View File
@@ -276,9 +276,9 @@ export class HttpService implements HttpServiceInterface {
}
private urlForUrlAndParams(url: string, params: HttpRequestParams) {
const keyValueString = Object.keys(params)
const keyValueString = Object.keys(params as Record<string, unknown>)
.map((key) => {
return key + '=' + encodeURIComponent(params[key] as string)
return key + '=' + encodeURIComponent((params as Record<string, unknown>)[key] as string)
})
.join('&')
@@ -0,0 +1,6 @@
export interface RecoveryKeyParamsRequestParams {
apiVersion: string
username: string
codeChallenge: string
recoveryCodes: string
}
@@ -0,0 +1,7 @@
export interface SignInWithRecoveryCodesRequestParams {
apiVersion: string
username: string
password: string
codeVerifier: string
recoveryCodes: string
}
+2
View File
@@ -5,6 +5,8 @@ export * from './Authenticator/GenerateAuthenticatorRegistrationOptionsRequestPa
export * from './Authenticator/ListAuthenticatorsRequestParams'
export * from './Authenticator/VerifyAuthenticatorAuthenticationResponseRequestParams'
export * from './Authenticator/VerifyAuthenticatorRegistrationResponseRequestParams'
export * from './Recovery/RecoveryKeyParamsRequestParams'
export * from './Recovery/SignInWithRecoveryCodesRequestParams'
export * from './Subscription/AppleIAPConfirmRequestParams'
export * from './Subscription/SubscriptionInviteAcceptRequestParams'
export * from './Subscription/SubscriptionInviteCancelRequestParams'
@@ -0,0 +1,10 @@
import { Either } from '@standardnotes/common'
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
import { HttpResponse } from '../../Http/HttpResponse'
import { GenerateRecoveryCodesResponseBody } from './GenerateRecoveryCodesResponseBody'
export interface GenerateRecoveryCodesResponse extends HttpResponse {
data: Either<GenerateRecoveryCodesResponseBody, HttpErrorResponseBody>
}
@@ -0,0 +1,3 @@
export interface GenerateRecoveryCodesResponseBody {
recoveryCodes: string
}
@@ -0,0 +1,10 @@
import { Either } from '@standardnotes/common'
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
import { HttpResponse } from '../../Http/HttpResponse'
import { RecoveryKeyParamsResponseBody } from './RecoveryKeyParamsResponseBody'
export interface RecoveryKeyParamsResponse extends HttpResponse {
data: Either<RecoveryKeyParamsResponseBody, HttpErrorResponseBody>
}
@@ -0,0 +1,5 @@
import { KeyParamsData } from '@standardnotes/responses'
export interface RecoveryKeyParamsResponseBody {
keyParams: KeyParamsData
}
@@ -0,0 +1,10 @@
import { Either } from '@standardnotes/common'
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
import { HttpResponse } from '../../Http/HttpResponse'
import { SignInWithRecoveryCodesResponseBody } from './SignInWithRecoveryCodesResponseBody'
export interface SignInWithRecoveryCodesResponse extends HttpResponse {
data: Either<SignInWithRecoveryCodesResponseBody, HttpErrorResponseBody>
}
@@ -0,0 +1,11 @@
import { KeyParamsData, SessionBody } from '@standardnotes/responses'
export interface SignInWithRecoveryCodesResponseBody {
session: SessionBody
key_params: KeyParamsData
user: {
uuid: string
email: string
protocolVersion: string
}
}
@@ -12,6 +12,12 @@ export * from './Authenticator/VerifyAuthenticatorAuthenticationResponseResponse
export * from './Authenticator/VerifyAuthenticatorAuthenticationResponseResponseBody'
export * from './Authenticator/VerifyAuthenticatorRegistrationResponseResponse'
export * from './Authenticator/VerifyAuthenticatorRegistrationResponseResponseBody'
export * from './Recovery/GenerateRecoveryCodesResponse'
export * from './Recovery/GenerateRecoveryCodesResponseBody'
export * from './Recovery/RecoveryKeyParamsResponse'
export * from './Recovery/RecoveryKeyParamsResponseBody'
export * from './Recovery/SignInWithRecoveryCodesResponse'
export * from './Recovery/SignInWithRecoveryCodesResponseBody'
export * from './Subscription/AppleIAPConfirmResponse'
export * from './Subscription/AppleIAPConfirmResponseBody'
export * from './Subscription/SubscriptionInviteAcceptResponse'
@@ -0,0 +1,33 @@
import { HttpServiceInterface } from '../../Http/HttpServiceInterface'
import { RecoveryKeyParamsRequestParams, SignInWithRecoveryCodesRequestParams } from '../../Request'
import {
GenerateRecoveryCodesResponse,
RecoveryKeyParamsResponse,
SignInWithRecoveryCodesResponse,
} from '../../Response'
import { AuthServerInterface } from './AuthServerInterface'
import { Paths } from './Paths'
export class AuthServer implements AuthServerInterface {
constructor(private httpService: HttpServiceInterface) {}
async generateRecoveryCodes(): Promise<GenerateRecoveryCodesResponse> {
const response = await this.httpService.post(Paths.v1.generateRecoveryCodes)
return response as GenerateRecoveryCodesResponse
}
async recoveryKeyParams(params: RecoveryKeyParamsRequestParams): Promise<RecoveryKeyParamsResponse> {
const response = await this.httpService.post(Paths.v1.recoveryKeyParams, params)
return response as RecoveryKeyParamsResponse
}
async signInWithRecoveryCodes(
params: SignInWithRecoveryCodesRequestParams,
): Promise<SignInWithRecoveryCodesResponse> {
const response = await this.httpService.post(Paths.v1.signInWithRecoveryCodes, params)
return response as SignInWithRecoveryCodesResponse
}
}
@@ -0,0 +1,12 @@
import { RecoveryKeyParamsRequestParams, SignInWithRecoveryCodesRequestParams } from '../../Request'
import {
GenerateRecoveryCodesResponse,
RecoveryKeyParamsResponse,
SignInWithRecoveryCodesResponse,
} from '../../Response'
export interface AuthServerInterface {
generateRecoveryCodes(): Promise<GenerateRecoveryCodesResponse>
recoveryKeyParams(params: RecoveryKeyParamsRequestParams): Promise<RecoveryKeyParamsResponse>
signInWithRecoveryCodes(params: SignInWithRecoveryCodesRequestParams): Promise<SignInWithRecoveryCodesResponse>
}
@@ -2,8 +2,15 @@ const SessionPaths = {
refreshSession: '/v1/sessions/refresh',
}
const RecoveryPaths = {
generateRecoveryCodes: '/v1/auth/recovery/codes',
recoveryKeyParams: '/v1/auth/recovery/login-params',
signInWithRecoveryCodes: '/v1/auth/recovery/login',
}
export const Paths = {
v1: {
...SessionPaths,
...RecoveryPaths,
},
}
+2
View File
@@ -1,3 +1,5 @@
export * from './Auth/AuthServer'
export * from './Auth/AuthServerInterface'
export * from './Authenticator/AuthenticatorServer'
export * from './Authenticator/AuthenticatorServerInterface'
export * from './Subscription/SubscriptionServer'
+48
View File
@@ -3,6 +3,54 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.104.77](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.76](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.75](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-07)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.74](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.73](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.72](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.71](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.70](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.69](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.68](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.67](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.66](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.104.65](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
**Note:** Version bump only for package @standardnotes/desktop
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.104.65",
"version": "3.104.77",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.20.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
### Features
* **snjs:** add sign in with recovery codes use case ([#2130](https://github.com/standardnotes/app/issues/2130)) ([be028ff](https://github.com/standardnotes/app/commit/be028ff87bb5306fc266b70567cbea8a13547e7e))
## [1.19.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/encryption
## [1.19.34](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/encryption
## [1.19.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
**Note:** Version bump only for package @standardnotes/encryption
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/encryption",
"version": "1.19.33",
"version": "1.20.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -32,6 +32,9 @@ export interface EncryptionProviderInterface {
getKeyEmbeddedKeyParams(key: EncryptedPayloadInterface): SNRootKeyParams | undefined
computeRootKey(password: string, keyParams: SNRootKeyParams): Promise<RootKeyInterface>
supportedVersions(): ProtocolVersion[]
isVersionNewerThanLibraryVersion(version: ProtocolVersion): boolean
platformSupportsKeyDerivation(keyParams: SNRootKeyParams): boolean
computeWrappingKey(passcode: string): Promise<RootKeyInterface>
getUserVersion(): ProtocolVersion | undefined
decryptBackupFile(
file: BackupFile,
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.58.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
### Features
* Added per-tag preference to use table layout and removed "Files Table View" from Labs ([dd8ccde](https://github.com/standardnotes/app/commit/dd8ccdeadc6c2fd7f7ccd66f2dc4b0081616fb2b))
## [1.57.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/features
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/features",
"version": "1.57.1",
"version": "1.58.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -38,8 +38,6 @@ export enum FeatureIdentifier {
TaskEditor = 'org.standardnotes.simple-task-editor',
TokenVaultEditor = 'org.standardnotes.token-vault',
FilesTableView = 'org.standardnotes.files-table-view',
DeprecatedBoldEditor = 'org.standardnotes.bold-editor',
DeprecatedMarkdownBasicEditor = 'org.standardnotes.simple-markdown-editor',
DeprecatedMarkdownMathEditor = 'org.standardnotes.fancy-markdown-editor',
@@ -53,4 +51,4 @@ export enum FeatureIdentifier {
*/
export const LegacyFileSafeIdentifier = 'org.standardnotes.legacy.file-safe'
export const ExperimentalFeatures = [FeatureIdentifier.FilesTableView]
export const ExperimentalFeatures = []
@@ -1,18 +1,5 @@
import { RoleName, SubscriptionName } from '@standardnotes/common'
import { FeatureDescription } from '../Feature/FeatureDescription'
import { FeatureIdentifier } from '../Feature/FeatureIdentifier'
import { PermissionName } from '../Permission/PermissionName'
export function experimentalFeatures(): FeatureDescription[] {
const filesTableView: FeatureDescription = {
identifier: FeatureIdentifier.FilesTableView,
name: 'Files Table View',
description:
'Replaces the current Files view with a table view, with name, size, and date sort options. Requires reload to take effect.',
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
availableInRoles: [RoleName.PlusUser, RoleName.ProUser],
permission_name: PermissionName.FilesTableView,
}
return [filesTableView]
return []
}
@@ -40,5 +40,4 @@ export enum PermissionName {
TwoFactorAuth = 'server:two-factor-auth',
SubscriptionSharing = 'server:subscription-sharing',
SuperEditor = 'editor:super-editor',
FilesTableView = 'app:files-table-view',
}
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.28.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.28.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.28.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/filepicker
# [1.28.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/filepicker",
"version": "1.28.0",
"version": "1.28.3",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.14.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
**Note:** Version bump only for package @standardnotes/files
## [1.14.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/files
## [1.14.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/files
## [1.14.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
**Note:** Version bump only for package @standardnotes/files
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files",
"version": "1.14.10",
"version": "1.14.13",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+52
View File
@@ -3,6 +3,58 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.50.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
**Note:** Version bump only for package @standardnotes/mobile
## [3.50.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
**Note:** Version bump only for package @standardnotes/mobile
## [3.50.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-07)
**Note:** Version bump only for package @standardnotes/mobile
## [3.50.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/mobile
## [3.50.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/mobile
## [3.50.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/mobile
## [3.50.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
**Note:** Version bump only for package @standardnotes/mobile
## [3.50.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
**Note:** Version bump only for package @standardnotes/mobile
## [3.50.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
**Note:** Version bump only for package @standardnotes/mobile
## [3.50.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/mobile
## [3.50.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/mobile
## [3.50.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/mobile
## [3.50.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
**Note:** Version bump only for package @standardnotes/mobile
# [3.50.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
### Features
-6
View File
@@ -1,16 +1,10 @@
for_platform :ios do
for_lane :dev do
app_identifier "com.standardnotes.standardnotes.dev"
end
for_lane :prod do
app_identifier "com.standardnotes.standardnotes"
end
end
for_platform :android do
for_lane :dev do
package_name 'com.standardnotes.dev'
end
for_lane :prod do
package_name 'com.standardnotes'
end
+1 -1
View File
@@ -2,7 +2,7 @@ storage_mode("git")
# type("development") # The default type, can be: appstore, adhoc, enterprise or development
app_identifier(["com.standardnotes.standardnotes", "com.standardnotes.standardnotes.dev"])
app_identifier(["com.standardnotes.standardnotes"])
# For all available options run `fastlane match --help`
# Remove the # in the beginning of the line to enable the other options
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.50.0",
"version": "3.50.13",
"author": "Standard Notes.",
"private": true,
"license": "AGPL-3.0-or-later",
+10
View File
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.42.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
### Features
* Added per-tag preference to use table layout and removed "Files Table View" from Labs ([dd8ccde](https://github.com/standardnotes/app/commit/dd8ccdeadc6c2fd7f7ccd66f2dc4b0081616fb2b))
## [1.41.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/models
# [1.41.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/models",
"version": "1.41.0",
"version": "1.42.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -18,4 +18,5 @@ export interface TagPreferences {
editorIdentifier?: EditorIdentifier
entryMode?: 'normal' | 'daily'
panelWidth?: number
useTableView?: boolean
}
+52
View File
@@ -3,6 +3,58 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.100](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.99](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.98](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-07)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.97](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.96](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.95](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.94](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.93](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.92](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.91](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.90](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.89](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.88](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.87](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
**Note:** Version bump only for package @standardnotes/releases
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/releases",
"version": "1.4.87",
"version": "1.4.100",
"license": "AGPL-3.0-or-later",
"main": "dist/releases.json",
"types": "dist/index.d.ts",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.12.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/responses
## [1.12.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/responses
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/responses",
"version": "1.12.12",
"version": "1.12.13",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.53.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
### Features
* **snjs:** add sign in with recovery codes use case ([#2130](https://github.com/standardnotes/app/issues/2130)) ([be028ff](https://github.com/standardnotes/app/commit/be028ff87bb5306fc266b70567cbea8a13547e7e))
## [1.52.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/services
## [1.52.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/services
# [1.52.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
### Features
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/services",
"version": "1.52.0",
"version": "1.53.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -19,6 +19,7 @@
"@standardnotes/api": "workspace:^",
"@standardnotes/auth": "^3.19.4",
"@standardnotes/common": "^1.45.0",
"@standardnotes/domain-core": "^1.11.0",
"@standardnotes/encryption": "workspace:^",
"@standardnotes/files": "workspace:^",
"@standardnotes/models": "workspace:^",
@@ -1,6 +1,7 @@
import { ApplicationIdentifier, ContentType } from '@standardnotes/common'
import { BackupFile, DecryptedItemInterface, ItemStream, Platform, PrefKey, PrefValue } from '@standardnotes/models'
import { FilesClientInterface } from '@standardnotes/files'
import { AlertService } from '../Alert/AlertService'
import { ComponentManagerInterface } from '../Component/ComponentManagerInterface'
import { ApplicationEvent } from '../Event/ApplicationEvent'
@@ -0,0 +1,28 @@
import { AnyKeyParamsContent } from '@standardnotes/common'
import { SessionBody } from '@standardnotes/responses'
export interface AuthClientInterface {
generateRecoveryCodes(): Promise<string | false>
recoveryKeyParams(dto: {
username: string
codeChallenge: string
recoveryCodes: string
}): Promise<AnyKeyParamsContent | false>
signInWithRecoveryCodes(dto: {
username: string
password: string
codeVerifier: string
recoveryCodes: string
}): Promise<
| {
keyParams: AnyKeyParamsContent
session: SessionBody
user: {
uuid: string
email: string
protocolVersion: string
}
}
| false
>
}
@@ -0,0 +1,82 @@
import { AuthApiServiceInterface } from '@standardnotes/api'
import { AnyKeyParamsContent } from '@standardnotes/common'
import { SessionBody } from '@standardnotes/responses'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { AbstractService } from '../Service/AbstractService'
import { AuthClientInterface } from './AuthClientInterface'
export class AuthManager extends AbstractService implements AuthClientInterface {
constructor(
private authApiService: AuthApiServiceInterface,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
}
async generateRecoveryCodes(): Promise<string | false> {
try {
const result = await this.authApiService.generateRecoveryCodes()
if (result.data.error) {
return false
}
return result.data.recoveryCodes
} catch (error) {
return false
}
}
async recoveryKeyParams(dto: {
username: string
codeChallenge: string
recoveryCodes: string
}): Promise<AnyKeyParamsContent | false> {
try {
const result = await this.authApiService.recoveryKeyParams(dto)
if (result.data.error) {
return false
}
return result.data.keyParams as AnyKeyParamsContent
} catch (error) {
return false
}
}
async signInWithRecoveryCodes(dto: {
username: string
password: string
codeVerifier: string
recoveryCodes: string
}): Promise<
| {
keyParams: AnyKeyParamsContent
session: SessionBody
user: {
uuid: string
email: string
protocolVersion: string
}
}
| false
> {
try {
const result = await this.authApiService.signInWithRecoveryCodes(dto)
if (result.data.error) {
return false
}
return {
keyParams: result.data.key_params as AnyKeyParamsContent,
session: result.data.session,
user: result.data.user,
}
} catch (error) {
return false
}
}
}
@@ -1,7 +1,8 @@
import { UserRegistrationResponseBody } from '@standardnotes/api'
import { ProtocolVersion } from '@standardnotes/common'
import { SNRootKey } from '@standardnotes/encryption'
import { RootKeyInterface } from '@standardnotes/models'
import { ClientDisplayableError, HttpResponse, SignInResponse, User } from '@standardnotes/responses'
import { ClientDisplayableError, HttpResponse, SessionBody, SignInResponse, User } from '@standardnotes/responses'
import { Base64String } from '@standardnotes/sncrypto-common'
import { SessionManagerResponse } from './SessionManagerResponse'
@@ -31,4 +32,13 @@ export interface SessionsClientInterface {
wrappingKey?: RootKeyInterface
newEmail?: string
}): Promise<SessionManagerResponse>
handleAuthentication(dto: {
session: SessionBody
user: {
uuid: string
email: string
}
rootKey: SNRootKey
wrappingKey?: SNRootKey
}): Promise<void>
}
@@ -182,5 +182,6 @@ export const ErrorAlertStrings = {
export const KeychainRecoveryStrings = {
Title: 'Restore Keychain',
Text: "We've detected that your keychain has been wiped. This can happen when restoring your device from a backup. Please enter your account password to restore your account keys.",
Text: (email: string) =>
`We've detected that your keychain has been wiped. This can happen when restoring your device from a backup. Please enter your account password for "${email}" to restore your account keys.`,
}
@@ -1,3 +1,4 @@
import { Base64String } from '@standardnotes/sncrypto-common'
import { UserRequestType } from '@standardnotes/common'
import { DeinitSource } from '../Application/DeinitSource'
@@ -8,4 +9,5 @@ export interface UserClientInterface {
}>
signOut(force?: boolean, source?: DeinitSource): Promise<void>
submitUserRequest(requestType: UserRequestType): Promise<boolean>
populateSessionFromDemoShareToken(token: Base64String): Promise<void>
}
@@ -1,6 +1,7 @@
import { Base64String } from '@standardnotes/sncrypto-common'
import { EncryptionProviderInterface, SNRootKey, SNRootKeyParams } from '@standardnotes/encryption'
import { HttpResponse, SignInResponse, User } from '@standardnotes/responses'
import { KeyParamsOrigination, UserRequestType } from '@standardnotes/common'
import { Either, KeyParamsOrigination, UserRequestType } from '@standardnotes/common'
import { UuidGenerator } from '@standardnotes/utils'
import { UserApiServiceInterface, UserRegistrationResponseBody } from '@standardnotes/api'
@@ -24,6 +25,8 @@ import { DeinitSource } from '../Application/DeinitSource'
import { StoragePersistencePolicies } from '../Storage/StorageTypes'
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
import { ProtectionsClientInterface } from '../Protection/ProtectionClientInterface'
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
import { InternalEventInterface } from '../Internal/InternalEventInterface'
export type CredentialsChangeFunctionResponse = { error?: { message: string } }
export type AccountServiceResponse = HttpResponse
@@ -33,11 +36,25 @@ export enum AccountEvent {
SignedOut = 'SignedOut',
}
type AccountEventData = {
export interface SignedInOrRegisteredEventPayload {
ephemeral: boolean
mergeLocal: boolean
awaitSync: boolean
checkIntegrity: boolean
}
export interface SignedOutEventPayload {
source: DeinitSource
}
export class UserService extends AbstractService<AccountEvent, AccountEventData> implements UserClientInterface {
export interface AccountEventData {
payload: Either<SignedInOrRegisteredEventPayload, SignedOutEventPayload>
}
export class UserService
extends AbstractService<AccountEvent, AccountEventData>
implements UserClientInterface, InternalEventHandlerInterface
{
private signingIn = false
private registering = false
@@ -59,6 +76,43 @@ export class UserService extends AbstractService<AccountEvent, AccountEventData>
super(internalEventBus)
}
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === AccountEvent.SignedInOrRegistered) {
const payload = (event.payload as AccountEventData).payload as SignedInOrRegisteredEventPayload
this.syncService.resetSyncState()
await this.storageService.setPersistencePolicy(
payload.ephemeral ? StoragePersistencePolicies.Ephemeral : StoragePersistencePolicies.Default,
)
if (payload.mergeLocal) {
await this.syncService.markAllItemsAsNeedingSyncAndPersist()
} else {
void this.itemManager.removeAllItemsFromMemory()
await this.clearDatabase()
}
this.unlockSyncing()
const syncPromise = this.syncService
.downloadFirstSync(1_000, {
checkIntegrity: payload.checkIntegrity,
awaitAll: payload.awaitSync,
})
.then(() => {
if (!payload.awaitSync) {
void this.protocolService.decryptErroredPayloads()
}
})
if (payload.awaitSync) {
await syncPromise
await this.protocolService.decryptErroredPayloads()
}
}
}
public override deinit(): void {
super.deinit()
;(this.sessionManager as unknown) = undefined
@@ -96,27 +150,17 @@ export class UserService extends AbstractService<AccountEvent, AccountEventData>
this.lockSyncing()
const response = await this.sessionManager.register(email, password, ephemeral)
this.syncService.resetSyncState()
await this.notifyEventSync(AccountEvent.SignedInOrRegistered, {
payload: {
ephemeral,
mergeLocal,
awaitSync: true,
checkIntegrity: false,
},
})
await this.storageService.setPersistencePolicy(
ephemeral ? StoragePersistencePolicies.Ephemeral : StoragePersistencePolicies.Default,
)
if (mergeLocal) {
await this.syncService.markAllItemsAsNeedingSyncAndPersist()
} else {
await this.itemManager.removeAllItemsFromMemory()
await this.clearDatabase()
}
await this.notifyEvent(AccountEvent.SignedInOrRegistered)
this.unlockSyncing()
this.registering = false
await this.syncService.downloadFirstSync(300)
void this.protocolService.decryptErroredPayloads()
return response
} catch (error) {
this.unlockSyncing()
@@ -155,39 +199,15 @@ export class UserService extends AbstractService<AccountEvent, AccountEventData>
const result = await this.sessionManager.signIn(email, password, strict, ephemeral)
if (!result.response.error) {
this.syncService.resetSyncState()
await this.storageService.setPersistencePolicy(
ephemeral ? StoragePersistencePolicies.Ephemeral : StoragePersistencePolicies.Default,
)
if (mergeLocal) {
await this.syncService.markAllItemsAsNeedingSyncAndPersist()
} else {
void this.itemManager.removeAllItemsFromMemory()
await this.clearDatabase()
}
await this.notifyEvent(AccountEvent.SignedInOrRegistered)
this.unlockSyncing()
const syncPromise = this.syncService
.downloadFirstSync(1_000, {
const notifyingFunction = awaitSync ? this.notifyEventSync.bind(this) : this.notifyEvent.bind(this)
await notifyingFunction(AccountEvent.SignedInOrRegistered, {
payload: {
mergeLocal,
awaitSync,
ephemeral,
checkIntegrity: true,
awaitAll: awaitSync,
})
.then(() => {
if (!awaitSync) {
void this.protocolService.decryptErroredPayloads()
}
})
if (awaitSync) {
await syncPromise
await this.protocolService.decryptErroredPayloads()
}
},
})
} else {
this.unlockSyncing()
}
@@ -266,15 +286,14 @@ export class UserService extends AbstractService<AccountEvent, AccountEventData>
)
if (!response.error) {
await this.notifyEvent(AccountEvent.SignedInOrRegistered)
this.unlockSyncing()
void this.syncService.downloadFirstSync(1_000, {
checkIntegrity: true,
await this.notifyEvent(AccountEvent.SignedInOrRegistered, {
payload: {
mergeLocal: true,
awaitSync: true,
ephemeral: false,
checkIntegrity: true,
},
})
void this.protocolService.decryptErroredPayloads()
}
this.unlockSyncing()
@@ -309,7 +328,7 @@ export class UserService extends AbstractService<AccountEvent, AccountEventData>
await this.sessionManager.signOut()
await this.protocolService.deleteWorkspaceSpecificKeyStateFromDevice()
await this.storageService.clearAllData()
await this.notifyEvent(AccountEvent.SignedOut, { source })
await this.notifyEvent(AccountEvent.SignedOut, { payload: { source } })
}
if (force) {
@@ -470,6 +489,18 @@ export class UserService extends AbstractService<AccountEvent, AccountEventData>
}
}
public async populateSessionFromDemoShareToken(token: Base64String): Promise<void> {
await this.sessionManager.populateSessionFromDemoShareToken(token)
await this.notifyEvent(AccountEvent.SignedInOrRegistered, {
payload: {
ephemeral: false,
mergeLocal: false,
checkIntegrity: false,
awaitSync: true,
},
})
}
private async setPasscodeWithoutWarning(passcode: string, origination: KeyParamsOrigination) {
const identifier = UuidGenerator.GenerateUuid()
const key = await this.protocolService.createRootKey(identifier, passcode, origination)
+2
View File
@@ -6,6 +6,8 @@ export * from './Application/ApplicationStage'
export * from './Application/DeinitCallback'
export * from './Application/DeinitSource'
export * from './Application/DeinitMode'
export * from './Auth/AuthClientInterface'
export * from './Auth/AuthManager'
export * from './Authenticator/AuthenticatorClientInterface'
export * from './Authenticator/AuthenticatorManager'
export * from './User/UserClientInterface'
+20
View File
@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.160.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
### Features
* **snjs:** add getting recovery codes ([#2132](https://github.com/standardnotes/app/issues/2132)) ([10751ea](https://github.com/standardnotes/app/commit/10751ea2edb2ca48e4493b87053383f27460ef75))
# [2.159.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
### Features
* **snjs:** add sign in with recovery codes use case ([#2130](https://github.com/standardnotes/app/issues/2130)) ([be028ff](https://github.com/standardnotes/app/commit/be028ff87bb5306fc266b70567cbea8a13547e7e))
## [2.158.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/snjs
## [2.158.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/snjs
# [2.158.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
### Features
+1 -1
View File
@@ -53,7 +53,7 @@ Once the server infrastructure is ready, and you've built all packages, you can
In app repo:
```
yarn start:server:e2e
yarn e2e
```
Once you are finished you can close the running local server on E2E repo by typing:
+51 -3
View File
@@ -1,8 +1,10 @@
import {
AuthApiService,
AuthenticatorApiService,
AuthenticatorApiServiceInterface,
AuthenticatorServer,
AuthenticatorServerInterface,
AuthServer,
HttpService,
HttpServiceInterface,
SubscriptionApiService,
@@ -69,6 +71,8 @@ import {
AccountEvent,
AuthenticatorClientInterface,
AuthenticatorManager,
AuthClientInterface,
AuthManager,
} from '@standardnotes/services'
import { BackupServiceInterface, FilesClientInterface } from '@standardnotes/files'
import { ComputePrivateUsername } from '@standardnotes/encryption'
@@ -88,9 +92,12 @@ import { SNLog } from '../Log'
import { ChallengeResponse, ListedClientInterface } from '../Services'
import { ApplicationConstructorOptions, FullyResolvedApplicationOptions } from './Options/ApplicationOptions'
import { ApplicationOptionsDefaults } from './Options/Defaults'
import { LegacySession, MapperInterface, Session } from '@standardnotes/domain-core'
import { LegacySession, MapperInterface, Session, UseCaseInterface } from '@standardnotes/domain-core'
import { SessionStorageMapper } from '@Lib/Services/Mapping/SessionStorageMapper'
import { LegacySessionStorageMapper } from '@Lib/Services/Mapping/LegacySessionStorageMapper'
import { SignInWithRecoveryCodes } from '@Lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes'
import { UseCaseContainerInterface } from '@Lib/Domain/UseCase/UseCaseContainerInterface'
import { GetRecoveryCodes } from '@Lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes'
/** How often to automatically sync, in milliseconds */
const DEFAULT_AUTO_SYNC_INTERVAL = 30_000
@@ -106,7 +113,7 @@ type ApplicationObserver = {
type ObserverRemover = () => void
export class SNApplication implements ApplicationInterface, AppGroupManagedApplication {
export class SNApplication implements ApplicationInterface, AppGroupManagedApplication, UseCaseContainerInterface {
onDeinit!: ExternalServices.DeinitCallback
/**
@@ -168,6 +175,10 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
private declare authenticatorApiService: AuthenticatorApiServiceInterface
private declare authenticatorServer: AuthenticatorServerInterface
private declare authenticatorManager: AuthenticatorClientInterface
private declare authManager: AuthClientInterface
private declare _signInWithRecoveryCodes: SignInWithRecoveryCodes
private declare _getRecoveryCodes: GetRecoveryCodes
private internalEventBus!: ExternalServices.InternalEventBusInterface
@@ -250,6 +261,14 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
return this.workspaceManager
}
get signInWithRecoveryCodes(): UseCaseInterface<void> {
return this._signInWithRecoveryCodes
}
get getRecoveryCodes(): UseCaseInterface<string> {
return this._getRecoveryCodes
}
public get files(): FilesClientInterface {
return this.fileService
}
@@ -1150,6 +1169,9 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.createAuthenticatorServer()
this.createAuthenticatorApiService()
this.createAuthenticatorManager()
this.createAuthManager()
this.createUseCases()
}
private clearServices() {
@@ -1200,6 +1222,9 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
;(this.authenticatorApiService as unknown) = undefined
;(this.authenticatorServer as unknown) = undefined
;(this.authenticatorManager as unknown) = undefined
;(this.authManager as unknown) = undefined
;(this._signInWithRecoveryCodes as unknown) = undefined
;(this._getRecoveryCodes as unknown) = undefined
this.services = []
}
@@ -1212,6 +1237,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.internalEventBus.addEventHandler(this.featuresService, ExternalServices.ApiServiceEvent.MetaReceived)
this.internalEventBus.addEventHandler(this.integrityService, ExternalServices.SyncEvent.SyncRequestsIntegrityCheck)
this.internalEventBus.addEventHandler(this.syncService, ExternalServices.IntegrityEvent.IntegrityCheckCompleted)
this.internalEventBus.addEventHandler(this.userService, AccountEvent.SignedInOrRegistered)
}
private clearInternalEventBus(): void {
@@ -1348,7 +1374,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
case AccountEvent.SignedOut: {
await this.notifyEvent(ApplicationEvent.SignedOut)
await this.prepareForDeinit()
this.deinit(this.getDeinitMode(), data?.source || DeinitSource.SignOut)
this.deinit(this.getDeinitMode(), data?.payload.source || DeinitSource.SignOut)
break
}
default: {
@@ -1581,6 +1607,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.identifier,
{
loadBatchSize: this.options.loadBatchSize,
sleepBetweenBatches: this.options.sleepBetweenBatches,
},
this.internalEventBus,
)
@@ -1738,4 +1765,25 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
private createAuthenticatorManager() {
this.authenticatorManager = new AuthenticatorManager(this.authenticatorApiService, this.internalEventBus)
}
private createAuthManager() {
const authServer = new AuthServer(this.httpService)
const authApiService = new AuthApiService(authServer)
this.authManager = new AuthManager(authApiService, this.internalEventBus)
}
private createUseCases() {
this._signInWithRecoveryCodes = new SignInWithRecoveryCodes(
this.authManager,
this.protocolService,
this.inMemoryStore,
this.options.crypto,
this.sessionManager,
this.internalEventBus,
)
this._getRecoveryCodes = new GetRecoveryCodes(this.authManager, this.settingsService)
}
}
@@ -1,9 +1,15 @@
import { ApplicationSyncOptions } from './OptionalOptions'
import { ApplicationDisplayOptions, ApplicationSyncOptions } from './OptionalOptions'
export interface ApplicationOptionsWhichHaveDefaults {
loadBatchSize: ApplicationSyncOptions['loadBatchSize']
sleepBetweenBatches: ApplicationSyncOptions['sleepBetweenBatches']
allowNoteSelectionStatePersistence: ApplicationDisplayOptions['allowNoteSelectionStatePersistence']
allowMultipleSelection: ApplicationDisplayOptions['allowMultipleSelection']
}
export const ApplicationOptionsDefaults: ApplicationOptionsWhichHaveDefaults = {
loadBatchSize: 700,
sleepBetweenBatches: 10,
allowMultipleSelection: true,
allowNoteSelectionStatePersistence: true,
}
@@ -3,10 +3,14 @@ export interface ApplicationSyncOptions {
* The size of the item batch to decrypt and render upon application load.
*/
loadBatchSize: number
sleepBetweenBatches: number
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ApplicationDisplayOptions {}
export interface ApplicationDisplayOptions {
allowNoteSelectionStatePersistence: boolean
allowMultipleSelection: boolean
}
export interface ApplicationOptionalConfiguratioOptions {
/**
@@ -0,0 +1,48 @@
import { AuthClientInterface } from '@standardnotes/services'
import { SettingsClientInterface } from '@Lib/Services/Settings/SettingsClientInterface'
import { GetRecoveryCodes } from './GetRecoveryCodes'
describe('GetRecoveryCodes', () => {
let authClient: AuthClientInterface
let settingsClient: SettingsClientInterface
const createUseCase = () => new GetRecoveryCodes(authClient, settingsClient)
beforeEach(() => {
authClient = {} as jest.Mocked<AuthClientInterface>
authClient.generateRecoveryCodes = jest.fn().mockResolvedValue('recovery-codes')
settingsClient = {} as jest.Mocked<SettingsClientInterface>
settingsClient.getSetting = jest.fn().mockResolvedValue('existing-recovery-codes')
})
it('should return existing recovery codes if they exist', async () => {
const useCase = createUseCase()
const result = await useCase.execute()
expect(result.getValue()).toBe('existing-recovery-codes')
})
it('should generate recovery codes if they do not exist', async () => {
settingsClient.getSetting = jest.fn().mockResolvedValue(undefined)
const useCase = createUseCase()
const result = await useCase.execute()
expect(result.getValue()).toBe('recovery-codes')
})
it('should return error if recovery codes could not be generated', async () => {
settingsClient.getSetting = jest.fn().mockResolvedValue(undefined)
authClient.generateRecoveryCodes = jest.fn().mockResolvedValue(false)
const useCase = createUseCase()
const result = await useCase.execute()
expect(result.isFailed()).toBe(true)
})
})
@@ -0,0 +1,23 @@
import { AuthClientInterface } from '@standardnotes/services'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { SettingName } from '@standardnotes/settings'
import { SettingsClientInterface } from '@Lib/Services/Settings/SettingsClientInterface'
export class GetRecoveryCodes implements UseCaseInterface<string> {
constructor(private authClient: AuthClientInterface, private settingsClient: SettingsClientInterface) {}
async execute(): Promise<Result<string>> {
const existingRecoveryCodes = await this.settingsClient.getSetting(SettingName.RecoveryCodes)
if (existingRecoveryCodes !== undefined) {
return Result.ok(existingRecoveryCodes)
}
const generatedRecoveryCodes = await this.authClient.generateRecoveryCodes()
if (generatedRecoveryCodes === false) {
return Result.fail('Could not generate recovery codes')
}
return Result.ok(generatedRecoveryCodes)
}
}
@@ -0,0 +1,179 @@
import {
AuthClientInterface,
InternalEventBusInterface,
KeyValueStoreInterface,
SessionsClientInterface,
} from '@standardnotes/services'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { AnyKeyParamsContent } from '@standardnotes/common'
import { DecryptedPayloadInterface, RootKeyContent, RootKeyInterface } from '@standardnotes/models'
import { SessionBody } from '@standardnotes/responses'
import { SignInWithRecoveryCodes } from './SignInWithRecoveryCodes'
describe('SignInWithRecoveryCodes', () => {
let authManager: AuthClientInterface
let protocolService: EncryptionProviderInterface
let inMemoryStore: KeyValueStoreInterface<string>
let crypto: PureCryptoInterface
let sessionManager: SessionsClientInterface
let internalEventBus: InternalEventBusInterface
const createUseCase = () => new SignInWithRecoveryCodes(
authManager,
protocolService,
inMemoryStore,
crypto,
sessionManager,
internalEventBus,
)
beforeEach(() => {
authManager = {} as jest.Mocked<AuthClientInterface>
authManager.recoveryKeyParams = jest.fn().mockReturnValue({
identifier: '[email protected]',
pw_nonce: 'pw_nonce',
created: new Date().toISOString(),
/** The event that lead to the creation of these params */
origination: 'register',
version: '004',
})
authManager.signInWithRecoveryCodes = jest.fn()
const rootKey = {
serverPassword: 'foobar',
} as jest.Mocked<RootKeyInterface>
const payload = {} as jest.Mocked<DecryptedPayloadInterface<RootKeyContent>>
payload.ejected = jest.fn().mockReturnValue({
uuid: 'uuid',
})
rootKey.payload = payload
protocolService = {} as jest.Mocked<EncryptionProviderInterface>
protocolService.hasAccount = jest.fn()
protocolService.computeRootKey = jest.fn().mockReturnValue(rootKey)
protocolService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(true)
protocolService.supportedVersions = jest.fn().mockReturnValue([
'001',
'002',
'003',
'004',
])
protocolService.isVersionNewerThanLibraryVersion = jest.fn()
inMemoryStore = {} as jest.Mocked<KeyValueStoreInterface<string>>
inMemoryStore.setValue = jest.fn()
inMemoryStore.removeValue = jest.fn()
crypto = {} as jest.Mocked<PureCryptoInterface>
crypto.generateRandomKey = jest.fn()
crypto.base64URLEncode = jest.fn()
crypto.sha256 = jest.fn()
sessionManager = {} as jest.Mocked<SessionsClientInterface>
sessionManager.handleAuthentication = jest.fn()
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
internalEventBus.publishSync = jest.fn()
})
it('should fail if an account already exists', async () => {
protocolService.hasAccount = jest.fn().mockReturnValue(true)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
expect(result.isFailed()).toBe(true)
expect(result.getError()).toEqual('Tried to sign in when an account already exists.')
})
it('should fail if recovery key params could not be retrieved', async () => {
authManager.recoveryKeyParams = jest.fn().mockReturnValue(false)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
expect(result.isFailed()).toBe(true)
expect(result.getError()).toEqual('Could not retrieve recovery key params')
})
it('should fail if key params has unsupported deriviation', async () => {
protocolService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
expect(result.isFailed()).toBe(true)
expect(result.getError()).toEqual('Your account was created on a platform with higher security capabilities than this browser supports. If we attempted to generate your login keys here, it would take hours. Please use a browser with more up to date security capabilities, like Google Chrome or Firefox, to log in.')
})
it('should fail if key params has unsupported version', async () => {
protocolService.isVersionNewerThanLibraryVersion = jest.fn().mockReturnValue(true)
authManager.recoveryKeyParams = jest.fn().mockReturnValue({
identifier: '[email protected]',
pw_nonce: 'pw_nonce',
created: new Date().toISOString(),
/** The event that lead to the creation of these params */
origination: 'register',
version: '006',
})
protocolService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
expect(result.isFailed()).toBe(true)
expect(result.getError()).toEqual('This version of the application does not support your newer account type. Please upgrade to the latest version of Standard Notes to sign in.')
})
it('should fail if key params has expired version', async () => {
protocolService.isVersionNewerThanLibraryVersion = jest.fn().mockReturnValue(false)
authManager.recoveryKeyParams = jest.fn().mockReturnValue({
identifier: '[email protected]',
pw_nonce: 'pw_nonce',
created: new Date().toISOString(),
/** The event that lead to the creation of these params */
origination: 'register',
version: '006',
})
protocolService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
expect(result.isFailed()).toBe(true)
expect(result.getError()).toEqual('The protocol version associated with your account is outdated and no longer supported by this application. Please visit standardnotes.com/help/security for more information.')
})
it('should fail if the sign in with recovery codes fails', async () => {
authManager.signInWithRecoveryCodes = jest.fn().mockReturnValue(false)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
expect(result.isFailed()).toBe(true)
expect(result.getError()).toEqual('Could not sign in with recovery codes')
})
it('should sign in with recovery codes', async () => {
authManager.signInWithRecoveryCodes = jest.fn().mockReturnValue({
keyParams: {} as AnyKeyParamsContent,
session: {} as SessionBody,
user: {
uuid: '1-2-3',
email: '[email protected]',
protocolVersion: '004',
}
})
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
expect(result.isFailed()).toBe(false)
})
})
@@ -0,0 +1,107 @@
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { CopyPayloadWithContentOverride } from '@standardnotes/models'
import {
AccountEvent,
AuthClientInterface,
EXPIRED_PROTOCOL_VERSION,
InternalEventBusInterface,
InternalEventPublishStrategy,
KeyValueStoreInterface,
SessionsClientInterface,
StorageKey,
UNSUPPORTED_KEY_DERIVATION,
UNSUPPORTED_PROTOCOL_VERSION,
} from '@standardnotes/services'
import { CreateAnyKeyParams, EncryptionProviderInterface, SNRootKey } from '@standardnotes/encryption'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { SignInWithRecoveryCodesDTO } from './SignInWithRecoveryCodesDTO'
export class SignInWithRecoveryCodes implements UseCaseInterface<void> {
constructor(
private authManager: AuthClientInterface,
private protocolService: EncryptionProviderInterface,
private inMemoryStore: KeyValueStoreInterface<string>,
private crypto: PureCryptoInterface,
private sessionManager: SessionsClientInterface,
private internalEventBus: InternalEventBusInterface,
) {}
async execute(dto: SignInWithRecoveryCodesDTO): Promise<Result<void>> {
if (this.protocolService.hasAccount()) {
return Result.fail('Tried to sign in when an account already exists.')
}
const codeVerifier = this.crypto.generateRandomKey(256)
this.inMemoryStore.setValue(StorageKey.CodeVerifier, codeVerifier)
const codeChallenge = this.crypto.base64URLEncode(await this.crypto.sha256(codeVerifier))
const recoveryKeyParams = await this.authManager.recoveryKeyParams({
codeChallenge,
...dto,
})
if (recoveryKeyParams === false) {
return Result.fail('Could not retrieve recovery key params')
}
const rootKeyParams = CreateAnyKeyParams(recoveryKeyParams)
if (!this.protocolService.supportedVersions().includes(rootKeyParams.version)) {
if (this.protocolService.isVersionNewerThanLibraryVersion(rootKeyParams.version)) {
return Result.fail(UNSUPPORTED_PROTOCOL_VERSION)
}
return Result.fail(EXPIRED_PROTOCOL_VERSION)
}
if (!this.protocolService.platformSupportsKeyDerivation(rootKeyParams)) {
return Result.fail(UNSUPPORTED_KEY_DERIVATION)
}
const rootKey = await this.protocolService.computeRootKey(dto.password, rootKeyParams)
const signInResult = await this.authManager.signInWithRecoveryCodes({
codeVerifier,
recoveryCodes: dto.recoveryCodes,
username: dto.username,
password: rootKey.serverPassword as string,
})
if (signInResult === false) {
return Result.fail('Could not sign in with recovery codes')
}
this.inMemoryStore.removeValue(StorageKey.CodeVerifier)
const expandedRootKey = new SNRootKey(
CopyPayloadWithContentOverride(rootKey.payload, {
keyParams: signInResult.keyParams,
}),
)
await this.sessionManager.handleAuthentication({
session: signInResult.session,
user: signInResult.user,
rootKey: expandedRootKey,
})
await this.internalEventBus.publishSync(
{
type: AccountEvent.SignedInOrRegistered,
payload: {
payload: {
ephemeral: false,
mergeLocal: false,
awaitSync: true,
checkIntegrity: false,
},
},
},
InternalEventPublishStrategy.SEQUENCE,
)
return Result.ok()
}
}
@@ -0,0 +1,5 @@
export interface SignInWithRecoveryCodesDTO {
recoveryCodes: string
username: string
password: string
}
@@ -0,0 +1,6 @@
import { UseCaseInterface } from '@standardnotes/domain-core'
export interface UseCaseContainerInterface {
get signInWithRecoveryCodes(): UseCaseInterface<void>
get getRecoveryCodes(): UseCaseInterface<string>
}
+4 -4
View File
@@ -1,4 +1,4 @@
import { AnyKeyParamsContent } from '@standardnotes/common'
import { AnyKeyParamsContent, KeyParamsContent004 } from '@standardnotes/common'
import { EncryptedPayload, EncryptedTransferPayload, isErrorDecryptingPayload } from '@standardnotes/models'
import { PreviousSnjsVersion1_0_0, PreviousSnjsVersion2_0_0, SnjsVersion } from '../Version'
import { Migration } from '@Lib/Migrations/Migration'
@@ -165,7 +165,7 @@ export class BaseMigration extends Migration {
}
private async repairMissingKeychain() {
const rawAccountParams = await this.reader.getAccountKeyParams()
const rawAccountParams = (await this.reader.getAccountKeyParams()) as AnyKeyParamsContent
/** Choose an item to decrypt against */
const allItems = (
@@ -196,14 +196,14 @@ export class BaseMigration extends Migration {
ChallengeReason.Custom,
false,
KeychainRecoveryStrings.Title,
KeychainRecoveryStrings.Text,
KeychainRecoveryStrings.Text((rawAccountParams as KeyParamsContent004).identifier),
)
return new Promise((resolve) => {
this.services.challengeService.addChallengeObserver(challenge, {
onNonvalidatedSubmit: async (challengeResponse) => {
const password = challengeResponse.values[0].value as string
const accountParams = this.services.protocolService.createKeyParams(rawAccountParams as AnyKeyParamsContent)
const accountParams = this.services.protocolService.createKeyParams(rawAccountParams)
const rootKey = await this.services.protocolService.computeRootKey(password, accountParams)
/** TS can't detect we returned early above if itemToDecrypt is null */
@@ -24,7 +24,7 @@ import {
Challenge,
} from '@standardnotes/services'
import { Base64String } from '@standardnotes/sncrypto-common'
import { ClientDisplayableError } from '@standardnotes/responses'
import { ClientDisplayableError, SessionBody } from '@standardnotes/responses'
import { CopyPayloadWithContentOverride } from '@standardnotes/models'
import { isNullOrUndefined } from '@standardnotes/utils'
import { LegacySession, MapperInterface, Session, SessionToken } from '@standardnotes/domain-core'
@@ -306,7 +306,12 @@ export class SNSessionManager extends AbstractService<SessionEvent> implements S
throw new ApiCallError((registerResponse.data as HttpErrorResponseBody).error.message)
}
await this.handleAuthResponse(registerResponse.data, rootKey, wrappingKey)
await this.handleAuthentication({
rootKey,
wrappingKey,
session: registerResponse.data.session,
user: registerResponse.data.user,
})
return registerResponse.data
}
@@ -640,22 +645,30 @@ export class SNSessionManager extends AbstractService<SessionEvent> implements S
this.setSession(session)
}
private async handleAuthResponse(body: UserRegistrationResponseBody, rootKey: SNRootKey, wrappingKey?: SNRootKey) {
async handleAuthentication(dto: {
session: SessionBody
user: {
uuid: string
email: string
}
rootKey: SNRootKey
wrappingKey?: SNRootKey
}): Promise<void> {
const session = this.createSession(
body.session.access_token,
body.session.access_expiration,
body.session.refresh_token,
body.session.refresh_expiration,
body.session.readonly_access,
dto.session.access_token,
dto.session.access_expiration,
dto.session.refresh_token,
dto.session.refresh_expiration,
dto.session.readonly_access,
)
if (session !== null) {
await this.populateSession(rootKey, body.user, session, this.apiService.getHost(), wrappingKey)
await this.populateSession(dto.rootKey, dto.user, session, this.apiService.getHost(), dto.wrappingKey)
}
}
/**
* @deprecated use handleAuthResponse instead
* @deprecated use handleAuthentication instead
*/
private async handleSuccessAuthResponse(
response: Responses.SignInResponse | Responses.ChangeCredentialsResponse,
+10 -2
View File
@@ -298,6 +298,9 @@ export class SNSyncService
? chunks.fullEntries.remainingChunks
: chunks.keys.remainingChunks
let chunkIndex = 0
const ChunkIndexOfContentTypePriorityItems = 0
for (const chunk of remainingChunks) {
const dbEntries = isChunkFullEntry(chunk)
? chunk.entries
@@ -314,7 +317,14 @@ export class SNSyncService
.filter(isNotUndefined)
await this.processPayloadBatch(payloads, totalProcessedCount, payloadCount)
const shouldSleepOnlyAfterFirstRegularBatch = chunkIndex > ChunkIndexOfContentTypePriorityItems
if (shouldSleepOnlyAfterFirstRegularBatch) {
await sleep(this.options.sleepBetweenBatches, false, 'Sleeping to allow interface to update')
}
totalProcessedCount += payloads.length
chunkIndex++
}
this.databaseLoaded = true
@@ -353,8 +363,6 @@ export class SNSyncService
if (currentPosition != undefined && payloadCount != undefined) {
this.opStatus.setDatabaseLoadStatus(currentPosition, payloadCount, false)
}
await sleep(1, false)
}
private setLastSyncToken(token: string) {
+12 -9
View File
@@ -20,16 +20,19 @@ describe('application instances', () => {
})
it('two distinct applications should not share model manager state', async () => {
const app1 = await Factory.createAndInitializeApplication('app1')
const app2 = await Factory.createAndInitializeApplication('app2')
expect(app1.payloadManager).to.equal(app1.payloadManager)
expect(app1.payloadManager).to.not.equal(app2.payloadManager)
const context1 = await Factory.createAppContext({ identifier: 'app1' })
const context2 = await Factory.createAppContext({ identifier: 'app2' })
await Promise.all([context1.launch(), context2.launch()])
await Factory.createMappedNote(app1)
expect(app1.itemManager.items.length).length.to.equal(BaseItemCounts.DefaultItems + 1)
expect(app2.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
await Factory.safeDeinit(app1)
await Factory.safeDeinit(app2)
expect(context1.application.payloadManager).to.equal(context1.application.payloadManager)
expect(context1.application.payloadManager).to.not.equal(context2.application.payloadManager)
await Factory.createMappedNote(context1.application)
expect(context1.application.itemManager.items.length).length.to.equal(BaseItemCounts.DefaultItems + 1)
expect(context2.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
await context1.deinit()
await context2.deinit()
})
it('two distinct applications should not share storage manager state', async () => {
+4 -2
View File
@@ -16,7 +16,9 @@ describe('basic auth', function () {
beforeEach(async function () {
localStorage.clear()
this.expectedItemCount = BaseItemCounts.DefaultItems
this.application = await Factory.createInitAppWithFakeCrypto()
this.context = await Factory.createAppContext()
await this.context.launch()
this.application = this.context.application
this.email = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid()
})
@@ -402,7 +404,7 @@ describe('basic auth', function () {
await this.application.syncService.markAllItemsAsNeedingSyncAndPersist()
await this.application.syncService.sync(syncOptions)
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
this.application = await this.context.signout()
expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
expect(this.application.payloadManager.invalidPayloads.length).to.equal(0)
+42
View File
@@ -163,6 +163,13 @@ export class AppContext {
return newApplication
}
async signout() {
await this.application.user.signOut()
await this.initialize()
await this.launch()
return this.application
}
syncWithIntegrityCheck() {
return this.application.sync.sync({ checkIntegrity: true, awaitAll: true })
}
@@ -189,11 +196,46 @@ export class AppContext {
})
}
awaitUserPrefsSingletonCreation() {
const preferences = this.application.preferencesService.preferences
if (preferences) {
return
}
let didCompleteRelevantSync = false
return new Promise((resolve) => {
this.application.syncService.addEventObserver((eventName, data) => {
if (!didCompleteRelevantSync) {
if (data?.savedPayloads) {
const matching = data.savedPayloads.find((p) => {
return p.content_type === ContentType.UserPrefs
})
if (matching) {
didCompleteRelevantSync = true
resolve()
}
}
}
})
})
}
awaitUserPrefsSingletonResolution() {
return new Promise((resolve) => {
this.application.preferencesService.addEventObserver((eventName) => {
if (eventName === PreferencesServiceEvent.PreferencesChanged) {
resolve()
}
})
})
}
async launch({ awaitDatabaseLoad = true, receiveChallenge } = { awaitDatabaseLoad: true }) {
await this.application.prepareForLaunch({
receiveChallenge: receiveChallenge || this.handleChallenge,
})
await this.application.launch(awaitDatabaseLoad)
await this.awaitUserPrefsSingletonCreation()
}
async deinit() {
@@ -21,7 +21,9 @@ describe('app models', () => {
beforeEach(async function () {
this.expectedItemCount = BaseItemCounts.DefaultItems
this.application = await Factory.createInitAppWithFakeCrypto()
this.context = await Factory.createAppContext()
this.application = this.context.application
await this.context.launch()
})
afterEach(async function () {
@@ -13,6 +13,7 @@ describe('importing', function () {
let application
let email
let password
let context
beforeEach(function () {
localStorage.clear()
@@ -20,11 +21,16 @@ describe('importing', function () {
const setup = async ({ fakeCrypto }) => {
expectedItemCount = BaseItemCounts.DefaultItems
if (fakeCrypto) {
application = await Factory.createInitAppWithFakeCrypto()
context = await Factory.createAppContext()
} else {
application = await Factory.createInitAppWithRealCrypto()
context = await Factory.createAppContextWithRealCrypto()
}
await context.launch()
application = context.application
email = UuidGenerator.GenerateUuid()
password = UuidGenerator.GenerateUuid()
Factory.handlePasswordChallenges(application, password)
@@ -9,7 +9,9 @@ const expect = chai.expect
describe('model manager mapping', () => {
beforeEach(async function () {
this.expectedItemCount = BaseItemCounts.DefaultItems
this.application = await Factory.createInitAppWithFakeCrypto()
this.context = await Factory.createAppContext()
await this.context.launch()
this.application = this.context.application
})
afterEach(async function () {
@@ -14,7 +14,9 @@ describe('notes and tags', () => {
beforeEach(async function () {
this.expectedItemCount = BaseItemCounts.DefaultItems
this.application = await Factory.createInitAppWithFakeCrypto()
this.context = await Factory.createAppContext()
await this.context.launch()
this.application = this.context.application
})
afterEach(async function () {
@@ -7,7 +7,9 @@ const expect = chai.expect
describe('tags as folders', () => {
beforeEach(async function () {
this.application = await Factory.createInitAppWithFakeCrypto()
this.context = await Factory.createAppContext()
await this.context.launch()
this.application = this.context.application
})
afterEach(async function () {
+12 -2
View File
@@ -7,7 +7,11 @@ const expect = chai.expect
describe('preferences', function () {
beforeEach(async function () {
localStorage.clear()
this.application = await Factory.createInitAppWithFakeCrypto()
this.context = await Factory.createAppContext()
await this.context.launch()
this.application = this.context.application
this.email = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid()
})
@@ -70,10 +74,16 @@ describe('preferences', function () {
await register.call(this)
await this.application.setPreference('editorLeft', 300)
await this.application.sync.sync()
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
this.application = await this.context.signout()
await this.application.setPreference('editorLeft', 200)
await this.application.signIn(this.email, this.password)
const promise = this.context.awaitUserPrefsSingletonResolution()
await this.application.sync.sync({ awaitAll: true })
await promise
const editorLeft = this.application.getPreference('editorLeft')
expect(editorLeft).to.equal(300)
})
+5 -1
View File
@@ -31,7 +31,11 @@ describe('singletons', function () {
beforeEach(async function () {
localStorage.clear()
this.expectedItemCount = BaseItemCounts.DefaultItems
this.application = await Factory.createInitAppWithFakeCrypto()
this.context = await Factory.createAppContext()
await this.context.launch()
this.application = this.context.application
this.email = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid()
this.registerUser = async () => {
+5 -2
View File
@@ -16,7 +16,9 @@ describe('storage manager', function () {
beforeEach(async function () {
localStorage.clear()
this.expectedKeyCount = BASE_KEY_COUNT
this.application = await Factory.createInitAppWithFakeCrypto(Environment.Mobile)
this.context = await Factory.createAppContext()
await this.context.launch()
this.application = this.context.application
this.email = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid()
})
@@ -221,7 +223,8 @@ describe('storage manager', function () {
expect(decrypted.content).to.be.an.instanceof(Object)
})
it('disabling storage encryption should store items without encryption', async function () {
/** @TODO: Storage encryption disable is no longer available, remove tests and associated functionality */
it.skip('disabling storage encryption should store items without encryption', async function () {
await Factory.registerUserToApplication({
application: this.application,
email: this.email,
@@ -13,7 +13,9 @@ describe('offline syncing', () => {
beforeEach(async function () {
this.expectedItemCount = BaseItemCounts.DefaultItems
this.application = await Factory.createInitAppWithFakeCrypto()
this.context = await Factory.createAppContext()
await this.context.launch()
this.application = this.context.application
})
afterEach(async function () {
@@ -95,7 +95,7 @@ describe('online syncing', function () {
await this.application.sync.sync(syncOptions)
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
this.application = await this.context.signout()
expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
+6 -2
View File
@@ -7,7 +7,11 @@ const expect = chai.expect
describe('upgrading', () => {
beforeEach(async function () {
localStorage.clear()
this.application = await Factory.createInitAppWithFakeCrypto()
this.context = await Factory.createAppContext()
await this.context.launch()
this.application = this.context.application
this.email = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid()
this.passcode = '1234'
@@ -170,7 +174,7 @@ describe('upgrading', () => {
/** Delete default items key that is created on launch */
const itemsKey = await this.application.protocolService.getSureDefaultItemsKey()
await this.application.itemManager.setItemToBeDeleted(itemsKey)
expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(0)
expect(Uuids(this.application.itemManager.getDisplayableItemsKeys()).includes(itemsKey.uuid)).to.equal(false)
Factory.createMappedNote(this.application)
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/snjs",
"version": "2.158.0",
"version": "2.160.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -45,7 +45,7 @@
"@standardnotes/responses": "workspace:*",
"@standardnotes/security": "^1.7.0",
"@standardnotes/services": "workspace:*",
"@standardnotes/settings": "^1.18.4",
"@standardnotes/settings": "^1.19.0",
"@standardnotes/sncrypto-common": "workspace:*",
"@standardnotes/sncrypto-web": "workspace:*",
"@standardnotes/utils": "workspace:*",
+10
View File
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/toast
## [1.3.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
### Bug Fixes
* Fixed issue where file upload toast would close before completion if you switched windows ([30dda73](https://github.com/standardnotes/app/commit/30dda73e9078bbccf91c3f43307250c84a17e8bd))
## [1.3.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
**Note:** Version bump only for package @standardnotes/toast
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/toast",
"version": "1.3.15",
"version": "1.3.17",
"private": true,
"main": "./src/index.ts",
"scripts": {
+5 -1
View File
@@ -27,8 +27,12 @@ export const ToastTimer: FunctionComponent<Props> = ({ toast, index }) => {
const remainingTimeRef = useRef(duration)
const dismissToastOnEnd = useCallback(() => {
if (!shouldAutoClose) {
return
}
dismissToast(toast.id)
}, [toast.id])
}, [shouldAutoClose, toast.id])
const clearTimer = useCallback(() => {
if (toastTimerIdRef.current) {
+32
View File
@@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.23.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
**Note:** Version bump only for package @standardnotes/ui-services
## [1.23.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
### Bug Fixes
* Fixed issue where the importer would not correctly parse Google Keep JSON notes ([2e79fc4](https://github.com/standardnotes/app/commit/2e79fc412c8b57b175381c4f59593a03906d89a1))
## [1.23.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/ui-services
## [1.23.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
**Note:** Version bump only for package @standardnotes/ui-services
# [1.23.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
### Features
* Added keyboard navigation to Files table view ([#2131](https://github.com/standardnotes/app/issues/2131)) ([ba017d4](https://github.com/standardnotes/app/commit/ba017d42f5145a3f9fb4a4610d7582bd8571b29c))
## [1.22.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/ui-services
## [1.22.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/ui-services
## [1.22.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
**Note:** Version bump only for package @standardnotes/ui-services
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/ui-services",
"version": "1.22.2",
"version": "1.23.4",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -98,13 +98,13 @@ export class GoogleKeepConverter {
static isValidGoogleKeepJson(json: any): boolean {
return (
json.title &&
json.textContent &&
json.userEditedTimestampUsec &&
typeof json.title === 'string' &&
typeof json.textContent === 'string' &&
typeof json.userEditedTimestampUsec === 'number' &&
typeof json.isArchived === 'boolean' &&
typeof json.isTrashed === 'boolean' &&
typeof json.isPinned === 'boolean' &&
json.color
typeof json.color === 'string'
)
}
@@ -10,4 +10,6 @@ export enum KeyboardKey {
Home = 'Home',
End = 'End',
Space = ' ',
PageUp = 'PageUp',
PageDown = 'PageDown',
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.15.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/utils
## [1.15.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
**Note:** Version bump only for package @standardnotes/utils
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/utils",
"version": "1.15.1",
"version": "1.15.2",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+2 -2
View File
@@ -519,9 +519,9 @@ export function truncateHexString(string: string, desiredBits: number) {
* When awaited, this function allows code execution to pause for a set time.
* Should be used primarily for testing.
*/
export async function sleep(milliseconds: number, warn = true): Promise<void> {
export async function sleep(milliseconds: number, warn = true, desc = ''): Promise<void> {
if (warn) {
console.warn(`Sleeping for ${milliseconds}ms`)
console.warn(`Sleeping for ${milliseconds}ms ${desc}`)
}
return new Promise<void>((resolve) => {
setTimeout(function () {
+61
View File
@@ -3,6 +3,67 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.136.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
**Note:** Version bump only for package @standardnotes/web
## [3.136.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
**Note:** Version bump only for package @standardnotes/web
## [3.136.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-07)
### Bug Fixes
* Fixed issue where search button would not show up in Files view ([340f4f7](https://github.com/standardnotes/app/commit/340f4f7f09c28f7818cb4a7f1ccaa387ef0283c9))
## [3.136.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
### Bug Fixes
* Fixed issue where the "Attached to" in table view would report an extra number of links ([482c4d1](https://github.com/standardnotes/app/commit/482c4d1ca4499ae4e80f592176648ee2c03f16b8))
## [3.136.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
**Note:** Version bump only for package @standardnotes/web
# [3.136.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
### Features
* Added per-tag preference to use table layout and removed "Files Table View" from Labs ([dd8ccde](https://github.com/standardnotes/app/commit/dd8ccdeadc6c2fd7f7ccd66f2dc4b0081616fb2b))
## [3.135.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
**Note:** Version bump only for package @standardnotes/web
# [3.135.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
### Features
* Added keyboard navigation to Files table view ([#2131](https://github.com/standardnotes/app/issues/2131)) ([ba017d4](https://github.com/standardnotes/app/commit/ba017d42f5145a3f9fb4a4610d7582bd8571b29c))
# [3.134.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
### Features
* Added linked items container & autocomplete input to file preview modal ([22c6e2c](https://github.com/standardnotes/app/commit/22c6e2c56c656cdbbc9788e5e9dc9112b22a9127))
# [3.133.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
### Features
* Added "Add tag" option to file context menu ([ba4b6a5](https://github.com/standardnotes/app/commit/ba4b6a580b2b18d2c383475c8723fb66fa9fecdf))
* Added file options menu to file preview modal ([e57b28f](https://github.com/standardnotes/app/commit/e57b28fe29f230754c247bf2a769355a7fbd30cb))
## [3.132.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/web
## [3.132.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
**Note:** Version bump only for package @standardnotes/web
# [3.132.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
### Features

Some files were not shown because too many files have changed in this diff Show More