@oxy.so/core 1.16.0 → 1.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +30 -1
- package/dist/cjs/crypto/identityLink.js +18 -0
- package/dist/cjs/i18n/locales/ar-SA.json +78 -3
- package/dist/cjs/i18n/locales/ca-ES.json +78 -3
- package/dist/cjs/i18n/locales/de-DE.json +78 -3
- package/dist/cjs/i18n/locales/en-US.json +75 -28
- package/dist/cjs/i18n/locales/es-ES.json +75 -28
- package/dist/cjs/i18n/locales/fr-FR.json +78 -3
- package/dist/cjs/i18n/locales/it-IT.json +78 -3
- package/dist/cjs/i18n/locales/ja-JP.json +78 -3
- package/dist/cjs/i18n/locales/ko-KR.json +78 -3
- package/dist/cjs/i18n/locales/locales/ar-SA.json +78 -3
- package/dist/cjs/i18n/locales/locales/ca-ES.json +78 -3
- package/dist/cjs/i18n/locales/locales/de-DE.json +78 -3
- package/dist/cjs/i18n/locales/locales/en-US.json +75 -28
- package/dist/cjs/i18n/locales/locales/es-ES.json +75 -28
- package/dist/cjs/i18n/locales/locales/fr-FR.json +78 -3
- package/dist/cjs/i18n/locales/locales/it-IT.json +78 -3
- package/dist/cjs/i18n/locales/locales/ja-JP.json +78 -3
- package/dist/cjs/i18n/locales/locales/ko-KR.json +78 -3
- package/dist/cjs/i18n/locales/locales/pt-PT.json +78 -3
- package/dist/cjs/i18n/locales/locales/zh-CN.json +78 -3
- package/dist/cjs/i18n/locales/pt-PT.json +78 -3
- package/dist/cjs/i18n/locales/zh-CN.json +78 -3
- package/dist/cjs/index.js +9 -42
- package/dist/cjs/mixins/OxyServices.auth.js +110 -17
- package/dist/cjs/mixins/OxyServices.identity.js +128 -8
- package/dist/cjs/mixins/OxyServices.user.js +42 -7
- package/dist/cjs/utils/authWebUrl.js +4 -5
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +30 -1
- package/dist/esm/crypto/identityLink.js +15 -0
- package/dist/esm/i18n/locales/ar-SA.json +78 -3
- package/dist/esm/i18n/locales/ca-ES.json +78 -3
- package/dist/esm/i18n/locales/de-DE.json +78 -3
- package/dist/esm/i18n/locales/en-US.json +75 -28
- package/dist/esm/i18n/locales/es-ES.json +75 -28
- package/dist/esm/i18n/locales/fr-FR.json +78 -3
- package/dist/esm/i18n/locales/it-IT.json +78 -3
- package/dist/esm/i18n/locales/ja-JP.json +78 -3
- package/dist/esm/i18n/locales/ko-KR.json +78 -3
- package/dist/esm/i18n/locales/locales/ar-SA.json +78 -3
- package/dist/esm/i18n/locales/locales/ca-ES.json +78 -3
- package/dist/esm/i18n/locales/locales/de-DE.json +78 -3
- package/dist/esm/i18n/locales/locales/en-US.json +75 -28
- package/dist/esm/i18n/locales/locales/es-ES.json +75 -28
- package/dist/esm/i18n/locales/locales/fr-FR.json +78 -3
- package/dist/esm/i18n/locales/locales/it-IT.json +78 -3
- package/dist/esm/i18n/locales/locales/ja-JP.json +78 -3
- package/dist/esm/i18n/locales/locales/ko-KR.json +78 -3
- package/dist/esm/i18n/locales/locales/pt-PT.json +78 -3
- package/dist/esm/i18n/locales/locales/zh-CN.json +78 -3
- package/dist/esm/i18n/locales/pt-PT.json +78 -3
- package/dist/esm/i18n/locales/zh-CN.json +78 -3
- package/dist/esm/index.js +4 -4
- package/dist/esm/mixins/OxyServices.auth.js +110 -17
- package/dist/esm/mixins/OxyServices.identity.js +128 -8
- package/dist/esm/mixins/OxyServices.user.js +42 -7
- package/dist/esm/utils/authWebUrl.js +4 -5
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +21 -0
- package/dist/types/crypto/identityLink.d.ts +8 -0
- package/dist/types/index.d.ts +2 -5
- package/dist/types/mixins/OxyServices.auth.d.ts +68 -25
- package/dist/types/mixins/OxyServices.identity.d.ts +34 -34
- package/dist/types/mixins/OxyServices.user.d.ts +29 -6
- package/dist/types/models/interfaces.d.ts +67 -13
- package/dist/types/server/securityHeaders.d.ts +3 -3
- package/dist/types/utils/authWebUrl.d.ts +4 -5
- package/package.json +3 -3
- package/src/HttpService.ts +35 -1
- package/src/__tests__/serviceIdentityWhenAnonymous.test.ts +159 -0
- package/src/crypto/__tests__/identityLink.test.ts +18 -0
- package/src/crypto/__tests__/identityProof.test.ts +6 -6
- package/src/crypto/identityLink.ts +18 -0
- package/src/i18n/locales/ar-SA.json +78 -3
- package/src/i18n/locales/ca-ES.json +78 -3
- package/src/i18n/locales/de-DE.json +78 -3
- package/src/i18n/locales/en-US.json +75 -28
- package/src/i18n/locales/es-ES.json +75 -28
- package/src/i18n/locales/fr-FR.json +78 -3
- package/src/i18n/locales/it-IT.json +78 -3
- package/src/i18n/locales/ja-JP.json +78 -3
- package/src/i18n/locales/ko-KR.json +78 -3
- package/src/i18n/locales/pt-PT.json +78 -3
- package/src/i18n/locales/zh-CN.json +78 -3
- package/src/index.ts +6 -48
- package/src/mixins/OxyServices.auth.ts +127 -24
- package/src/mixins/OxyServices.identity.ts +126 -8
- package/src/mixins/OxyServices.user.ts +51 -10
- package/src/mixins/__tests__/identityLink.test.ts +89 -0
- package/src/mixins/__tests__/notifications.read.test.ts +82 -0
- package/src/mixins/__tests__/preSessionSkipAuth.test.ts +1 -1
- package/src/mixins/__tests__/webauthnAuth.test.ts +75 -1
- package/src/models/interfaces.ts +67 -12
- package/src/server/securityHeaders.ts +3 -3
- package/src/utils/authWebUrl.ts +4 -5
- package/dist/cjs/crypto/identityMove.js +0 -142
- package/dist/cjs/crypto/webIdentityCarrier.js +0 -325
- package/dist/esm/crypto/identityMove.js +0 -128
- package/dist/esm/crypto/webIdentityCarrier.js +0 -303
- package/dist/types/crypto/identityMove.d.ts +0 -75
- package/dist/types/crypto/webIdentityCarrier.d.ts +0 -150
- package/src/crypto/__tests__/identityMove.test.ts +0 -146
- package/src/crypto/__tests__/webIdentityCarrier.test.ts +0 -169
- package/src/crypto/__tests__/webIdentityRecoveryMaterial.test.ts +0 -117
- package/src/crypto/identityMove.ts +0 -167
- package/src/crypto/webIdentityCarrier.ts +0 -394
|
@@ -50,7 +50,26 @@
|
|
|
50
50
|
"createInCommons": "Commons에서 나의 아이덴티티 만들기",
|
|
51
51
|
"createAccount": "계정 만들기",
|
|
52
52
|
"backToSignInLink": "이미 계정이 있으신가요? 로그인",
|
|
53
|
-
"webSubtitle": "비밀번호가 없습니다. 기기가 패스키를 만들고,
|
|
53
|
+
"webSubtitle": "비밀번호가 없습니다. 기기가 패스키를 만들고, 잃어버리면 복구 이메일로 다시 들어올 수 있습니다.",
|
|
54
|
+
"username": {
|
|
55
|
+
"label": "사용자 이름",
|
|
56
|
+
"placeholder": "yourname",
|
|
57
|
+
"required": "사용자 이름을 정하세요.",
|
|
58
|
+
"taken": "이미 사용 중인 사용자 이름입니다."
|
|
59
|
+
},
|
|
60
|
+
"email": {
|
|
61
|
+
"title": "복구 이메일",
|
|
62
|
+
"subtitle": "패스키를 잃어버리면 다시 들어올 수 있도록 여기로 코드를 보냅니다. 다른 용도로는 쓰지 않습니다.",
|
|
63
|
+
"label": "이메일",
|
|
64
|
+
"placeholder": "you@example.com",
|
|
65
|
+
"invalid": "올바른 이메일 주소를 입력하세요."
|
|
66
|
+
},
|
|
67
|
+
"passkey": {
|
|
68
|
+
"title": "패스키 만들기",
|
|
69
|
+
"subtitle": "지문, 얼굴 또는 화면 잠금으로 로그인합니다. 기억할 것은 없습니다.",
|
|
70
|
+
"action": "패스키 만들기"
|
|
71
|
+
},
|
|
72
|
+
"commonsInstead": "직접 키를 보관하고 싶으신가요? Commons 사용하기"
|
|
54
73
|
},
|
|
55
74
|
"accountSwitcher": {
|
|
56
75
|
"loading": "계정을 불러오는 중...",
|
|
@@ -225,7 +244,63 @@
|
|
|
225
244
|
"help": "도움말 및 피드백",
|
|
226
245
|
"signOut": "로그아웃",
|
|
227
246
|
"privacy": "개인정보 처리방침",
|
|
228
|
-
"terms": "서비스 약관"
|
|
229
|
-
|
|
247
|
+
"terms": "서비스 약관"
|
|
248
|
+
},
|
|
249
|
+
"recover": {
|
|
250
|
+
"title": "계정 복구",
|
|
251
|
+
"subtitle": "사용자 이름이나 복구 이메일을 입력하세요. 계정의 복구 이메일로 코드를 보냅니다.",
|
|
252
|
+
"identifier": {
|
|
253
|
+
"label": "사용자 이름 또는 이메일",
|
|
254
|
+
"placeholder": "yourname",
|
|
255
|
+
"required": "사용자 이름이나 이메일을 입력하세요."
|
|
256
|
+
},
|
|
257
|
+
"commonsNote": "Commons를 쓰는 계정인가요? Commons에서 복구 문구로 복구하세요.",
|
|
258
|
+
"passkey": {
|
|
259
|
+
"title": "새 패스키 추가",
|
|
260
|
+
"subtitle": "@{{username}}용. 잃어버린 패스키는 나중에 계정 보안 설정에서 삭제하세요.",
|
|
261
|
+
"action": "패스키 만들기"
|
|
262
|
+
},
|
|
263
|
+
"backToSignIn": "로그인으로 돌아가기"
|
|
264
|
+
},
|
|
265
|
+
"emailCode": {
|
|
266
|
+
"title": "이메일을 확인하세요",
|
|
267
|
+
"sentTo": "{{email}}(으)로 6자리 코드를 보냈습니다.",
|
|
268
|
+
"sentIfAccount": "이 계정에 복구 이메일이 있다면 6자리 코드를 보냈습니다.",
|
|
269
|
+
"label": "코드",
|
|
270
|
+
"resend": "새 코드 보내기",
|
|
271
|
+
"resent": "새 코드를 보냈습니다.",
|
|
272
|
+
"changeEmail": "다른 이메일 사용",
|
|
273
|
+
"errors": {
|
|
274
|
+
"codeInvalid": "코드가 올바르지 않거나 만료되었습니다.",
|
|
275
|
+
"tooManyAttempts": "틀린 코드가 너무 많습니다. 새 코드를 요청하세요.",
|
|
276
|
+
"expired": "시간이 너무 오래 걸렸습니다. 다시 시작하세요.",
|
|
277
|
+
"unavailable": "지금은 Oxy가 이메일을 보낼 수 없습니다. 나중에 다시 시도하세요.",
|
|
278
|
+
"rateLimited": "코드가 너무 많습니다. 나중에 다시 시도하세요."
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
"deleteAccount": {
|
|
282
|
+
"passkey": {
|
|
283
|
+
"subtitle": "패스키로 확인하세요. @{{username}}과(와) 그 안의 모든 것이 영구적으로 삭제됩니다.",
|
|
284
|
+
"action": "패스키로 삭제",
|
|
285
|
+
"done": "계정이 삭제되었습니다.",
|
|
286
|
+
"closeWindow": "이 창을 닫아도 됩니다.",
|
|
287
|
+
"commons": "이 계정은 Commons를 사용합니다. Oxy Commons의 설정 > 계정 삭제에서 해당 키로 삭제하세요."
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
"linkCommons": {
|
|
291
|
+
"title": "Commons 연결",
|
|
292
|
+
"subtitle": "직접 키를 보관하세요. Commons를 연결하면 이 계정은 오직 당신의 것이 됩니다. 복구 이메일은 삭제되고, Commons의 복구 문구로 다시 들어올 수 있습니다.",
|
|
293
|
+
"scan": "휴대폰에서 Commons를 열고 “웹에 Oxy 계정이 있어요”를 선택한 다음 이 코드를 스캔하세요.",
|
|
294
|
+
"waiting": "Commons를 기다리는 중…",
|
|
295
|
+
"compareTitle": "코드 확인",
|
|
296
|
+
"compare": "Commons에 같은 코드가 표시됩니다. 다르면 취소하세요.",
|
|
297
|
+
"confirm": "네, Commons 연결",
|
|
298
|
+
"cancel": "취소",
|
|
299
|
+
"doneTitle": "Commons가 연결되었습니다",
|
|
300
|
+
"done": "이 계정은 이제 자기 보관 계정입니다. 복구 이메일은 삭제되었습니다. Commons의 복구 문구로 다시 들어올 수 있습니다.",
|
|
301
|
+
"already": "이 계정은 이미 Commons를 사용합니다.",
|
|
302
|
+
"expired": "이 코드는 만료되었습니다.",
|
|
303
|
+
"renew": "새 코드 보기",
|
|
304
|
+
"failed": "Commons를 연결하지 못했습니다. 다시 시도하세요."
|
|
230
305
|
}
|
|
231
306
|
}
|
|
@@ -50,7 +50,26 @@
|
|
|
50
50
|
"createInCommons": "Crie a sua identidade no Commons",
|
|
51
51
|
"createAccount": "Criar conta",
|
|
52
52
|
"backToSignInLink": "Já tem uma conta? Inicie sessão",
|
|
53
|
-
"webSubtitle": "Sem palavra-passe
|
|
53
|
+
"webSubtitle": "Sem palavra-passe: o teu dispositivo cria uma passkey e um email de recuperação devolve-te o acesso se a perderes.",
|
|
54
|
+
"username": {
|
|
55
|
+
"label": "Nome de utilizador",
|
|
56
|
+
"placeholder": "oteunome",
|
|
57
|
+
"required": "Escolhe um nome de utilizador.",
|
|
58
|
+
"taken": "Esse nome de utilizador já está em uso."
|
|
59
|
+
},
|
|
60
|
+
"email": {
|
|
61
|
+
"title": "O teu email de recuperação",
|
|
62
|
+
"subtitle": "Se perderes a passkey, enviamos-te aqui um código para voltares a entrar. Não é usado para mais nada.",
|
|
63
|
+
"label": "Email",
|
|
64
|
+
"placeholder": "tu@exemplo.pt",
|
|
65
|
+
"invalid": "Escreve um email válido."
|
|
66
|
+
},
|
|
67
|
+
"passkey": {
|
|
68
|
+
"title": "Cria a tua passkey",
|
|
69
|
+
"subtitle": "É com ela que inicias sessão: impressão digital, rosto ou bloqueio do ecrã. Nada para memorizar.",
|
|
70
|
+
"action": "Criar passkey"
|
|
71
|
+
},
|
|
72
|
+
"commonsInstead": "Queres guardar a tua própria chave? Usa o Commons"
|
|
54
73
|
},
|
|
55
74
|
"accountSwitcher": {
|
|
56
75
|
"loading": "A carregar contas...",
|
|
@@ -225,7 +244,63 @@
|
|
|
225
244
|
"help": "Ajuda e feedback",
|
|
226
245
|
"signOut": "Terminar sessão",
|
|
227
246
|
"privacy": "Política de privacidade",
|
|
228
|
-
"terms": "Termos de serviço"
|
|
229
|
-
|
|
247
|
+
"terms": "Termos de serviço"
|
|
248
|
+
},
|
|
249
|
+
"recover": {
|
|
250
|
+
"title": "Recupera a tua conta",
|
|
251
|
+
"subtitle": "Escreve o teu nome de utilizador ou o email de recuperação. Enviamos um código para o email de recuperação da conta.",
|
|
252
|
+
"identifier": {
|
|
253
|
+
"label": "Utilizador ou email",
|
|
254
|
+
"placeholder": "oteunome",
|
|
255
|
+
"required": "Escreve o teu utilizador ou email."
|
|
256
|
+
},
|
|
257
|
+
"commonsNote": "A tua conta usa o Commons? Recupera-a no Commons com a tua frase de recuperação.",
|
|
258
|
+
"passkey": {
|
|
259
|
+
"title": "Adiciona uma nova passkey",
|
|
260
|
+
"subtitle": "Para @{{username}}. Remove as passkeys perdidas mais tarde nas definições de segurança da conta.",
|
|
261
|
+
"action": "Criar passkey"
|
|
262
|
+
},
|
|
263
|
+
"backToSignIn": "Voltar a iniciar sessão"
|
|
264
|
+
},
|
|
265
|
+
"emailCode": {
|
|
266
|
+
"title": "Vê o teu email",
|
|
267
|
+
"sentTo": "Enviámos um código de 6 dígitos para {{email}}.",
|
|
268
|
+
"sentIfAccount": "Se esta conta tiver um email de recuperação, enviámos-lhe um código de 6 dígitos.",
|
|
269
|
+
"label": "Código",
|
|
270
|
+
"resend": "Enviar um novo código",
|
|
271
|
+
"resent": "Enviámos um novo código.",
|
|
272
|
+
"changeEmail": "Usar outro email",
|
|
273
|
+
"errors": {
|
|
274
|
+
"codeInvalid": "Esse código não está certo ou expirou.",
|
|
275
|
+
"tooManyAttempts": "Demasiados códigos errados. Pede um novo.",
|
|
276
|
+
"expired": "Passou demasiado tempo. Recomeça.",
|
|
277
|
+
"unavailable": "O Oxy não consegue enviar emails agora. Tenta mais tarde.",
|
|
278
|
+
"rateLimited": "Demasiados códigos. Tenta mais tarde."
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
"deleteAccount": {
|
|
282
|
+
"passkey": {
|
|
283
|
+
"subtitle": "Confirma com a tua passkey. @{{username}} e tudo o que contém são eliminados para sempre.",
|
|
284
|
+
"action": "Eliminar com passkey",
|
|
285
|
+
"done": "A tua conta foi eliminada.",
|
|
286
|
+
"closeWindow": "Podes fechar esta janela.",
|
|
287
|
+
"commons": "Esta conta usa o Commons: elimina-a no Oxy Commons, Definições > Eliminar conta, com a sua chave."
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
"linkCommons": {
|
|
291
|
+
"title": "Associar o Commons",
|
|
292
|
+
"subtitle": "Guarda a tua própria chave: associa o Commons e esta conta passa a ser só tua. O email de recuperação é eliminado e a tua frase de recuperação no Commons é como voltas a entrar.",
|
|
293
|
+
"scan": "No telemóvel, abre o Commons, escolhe «Tenho uma conta Oxy na web» e digitaliza este código.",
|
|
294
|
+
"waiting": "À espera do Commons…",
|
|
295
|
+
"compareTitle": "Verifica o código",
|
|
296
|
+
"compare": "O Commons mostra o mesmo código. Se não mostrar, cancela.",
|
|
297
|
+
"confirm": "Sim, associar o Commons",
|
|
298
|
+
"cancel": "Cancelar",
|
|
299
|
+
"doneTitle": "O Commons está associado",
|
|
300
|
+
"done": "Esta conta passou a ser de autocustódia. O email de recuperação foi eliminado: a tua frase de recuperação no Commons é como voltas a entrar.",
|
|
301
|
+
"already": "Esta conta já usa o Commons.",
|
|
302
|
+
"expired": "Este código expirou.",
|
|
303
|
+
"renew": "Mostrar um novo código",
|
|
304
|
+
"failed": "Não foi possível associar o Commons. Tenta de novo."
|
|
230
305
|
}
|
|
231
306
|
}
|
|
@@ -50,7 +50,26 @@
|
|
|
50
50
|
"createInCommons": "在 Commons 中创建您的身份",
|
|
51
51
|
"createAccount": "创建账户",
|
|
52
52
|
"backToSignInLink": "已有账户?登录",
|
|
53
|
-
"webSubtitle": "
|
|
53
|
+
"webSubtitle": "无需密码:你的设备会创建一个通行密钥,丢失时可通过恢复邮箱重新进入。",
|
|
54
|
+
"username": {
|
|
55
|
+
"label": "用户名",
|
|
56
|
+
"placeholder": "yourname",
|
|
57
|
+
"required": "请选择用户名。",
|
|
58
|
+
"taken": "该用户名已被占用。"
|
|
59
|
+
},
|
|
60
|
+
"email": {
|
|
61
|
+
"title": "你的恢复邮箱",
|
|
62
|
+
"subtitle": "如果你丢失了通行密钥,我们会向这里发送验证码让你重新进入。它不作其他用途。",
|
|
63
|
+
"label": "邮箱",
|
|
64
|
+
"placeholder": "you@example.com",
|
|
65
|
+
"invalid": "请输入有效的邮箱地址。"
|
|
66
|
+
},
|
|
67
|
+
"passkey": {
|
|
68
|
+
"title": "创建你的通行密钥",
|
|
69
|
+
"subtitle": "用指纹、面容或屏幕锁登录,无需记住任何东西。",
|
|
70
|
+
"action": "创建通行密钥"
|
|
71
|
+
},
|
|
72
|
+
"commonsInstead": "想自己保管密钥?改用 Commons"
|
|
54
73
|
},
|
|
55
74
|
"accountSwitcher": {
|
|
56
75
|
"loading": "正在加载账户...",
|
|
@@ -225,7 +244,63 @@
|
|
|
225
244
|
"help": "帮助与反馈",
|
|
226
245
|
"signOut": "退出登录",
|
|
227
246
|
"privacy": "隐私政策",
|
|
228
|
-
"terms": "服务条款"
|
|
229
|
-
|
|
247
|
+
"terms": "服务条款"
|
|
248
|
+
},
|
|
249
|
+
"recover": {
|
|
250
|
+
"title": "恢复你的账户",
|
|
251
|
+
"subtitle": "输入你的用户名或恢复邮箱。我们会向该账户的恢复邮箱发送验证码。",
|
|
252
|
+
"identifier": {
|
|
253
|
+
"label": "用户名或邮箱",
|
|
254
|
+
"placeholder": "yourname",
|
|
255
|
+
"required": "请输入用户名或邮箱。"
|
|
256
|
+
},
|
|
257
|
+
"commonsNote": "你的账户使用 Commons?请在 Commons 中用恢复短语恢复。",
|
|
258
|
+
"passkey": {
|
|
259
|
+
"title": "添加新的通行密钥",
|
|
260
|
+
"subtitle": "用于 @{{username}}。丢失的通行密钥可稍后在账户安全设置中移除。",
|
|
261
|
+
"action": "创建通行密钥"
|
|
262
|
+
},
|
|
263
|
+
"backToSignIn": "返回登录"
|
|
264
|
+
},
|
|
265
|
+
"emailCode": {
|
|
266
|
+
"title": "查看你的邮箱",
|
|
267
|
+
"sentTo": "我们已向 {{email}} 发送了 6 位验证码。",
|
|
268
|
+
"sentIfAccount": "如果该账户有恢复邮箱,我们已向其发送 6 位验证码。",
|
|
269
|
+
"label": "验证码",
|
|
270
|
+
"resend": "发送新验证码",
|
|
271
|
+
"resent": "我们已发送新验证码。",
|
|
272
|
+
"changeEmail": "使用其他邮箱",
|
|
273
|
+
"errors": {
|
|
274
|
+
"codeInvalid": "验证码不正确或已过期。",
|
|
275
|
+
"tooManyAttempts": "错误次数过多,请重新获取验证码。",
|
|
276
|
+
"expired": "耗时过长,请重新开始。",
|
|
277
|
+
"unavailable": "Oxy 暂时无法发送邮件,请稍后再试。",
|
|
278
|
+
"rateLimited": "验证码请求过多,请稍后再试。"
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
"deleteAccount": {
|
|
282
|
+
"passkey": {
|
|
283
|
+
"subtitle": "请用通行密钥确认。@{{username}} 及其中的一切将被永久删除。",
|
|
284
|
+
"action": "用通行密钥删除",
|
|
285
|
+
"done": "你的账户已删除。",
|
|
286
|
+
"closeWindow": "你可以关闭此窗口。",
|
|
287
|
+
"commons": "此账户使用 Commons:请在 Oxy Commons 的“设置 > 删除账户”中用其密钥删除。"
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
"linkCommons": {
|
|
291
|
+
"title": "关联 Commons",
|
|
292
|
+
"subtitle": "自己保管密钥:关联 Commons 后,此账户只属于你。它的恢复邮箱会被删除,今后用 Commons 中的恢复短语找回账户。",
|
|
293
|
+
"scan": "在手机上打开 Commons,选择“我在网页上有 Oxy 账户”,然后扫描此码。",
|
|
294
|
+
"waiting": "正在等待 Commons…",
|
|
295
|
+
"compareTitle": "核对代码",
|
|
296
|
+
"compare": "Commons 会显示相同的代码。如果不同,请取消。",
|
|
297
|
+
"confirm": "是的,关联 Commons",
|
|
298
|
+
"cancel": "取消",
|
|
299
|
+
"doneTitle": "Commons 已关联",
|
|
300
|
+
"done": "此账户现在由你自己保管。它的恢复邮箱已删除:今后用 Commons 中的恢复短语找回账户。",
|
|
301
|
+
"already": "此账户已在使用 Commons。",
|
|
302
|
+
"expired": "此代码已过期。",
|
|
303
|
+
"renew": "显示新代码",
|
|
304
|
+
"failed": "无法关联 Commons,请重试。"
|
|
230
305
|
}
|
|
231
306
|
}
|
package/src/index.ts
CHANGED
|
@@ -344,55 +344,11 @@ export {
|
|
|
344
344
|
export type { AeadResult } from './crypto/aead';
|
|
345
345
|
export { deriveSharedSecret } from './crypto/ecdh';
|
|
346
346
|
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
export {
|
|
350
|
-
WEB_IDENTITY_PRF_INPUT,
|
|
351
|
-
WebIdentityUnlockError,
|
|
352
|
-
WEB_IDENTITY_PRF_OUTPUT_LENGTH,
|
|
353
|
-
addWrap,
|
|
354
|
-
deriveIdentityFromMnemonic,
|
|
355
|
-
deriveIdentityFromPrivateKey,
|
|
356
|
-
deriveIdentityFromRecoveryMaterial,
|
|
357
|
-
deriveKeyEncryptionKey,
|
|
358
|
-
generateDataKey,
|
|
359
|
-
generateWebIdentity,
|
|
360
|
-
isUsablePrfOutput,
|
|
361
|
-
markWrapVerified,
|
|
362
|
-
normalizeMnemonic,
|
|
363
|
-
openWebIdentity,
|
|
364
|
-
parseRecoveryMaterial,
|
|
365
|
-
removeWrap,
|
|
366
|
-
sealWebIdentity,
|
|
367
|
-
unlockWebIdentity,
|
|
368
|
-
unwrapDataKey,
|
|
369
|
-
wipeBytes,
|
|
370
|
-
wipeOpenedIdentity,
|
|
371
|
-
} from './crypto/webIdentityCarrier';
|
|
372
|
-
export type {
|
|
373
|
-
OpenedMnemonicIdentity,
|
|
374
|
-
OpenedRawKeyIdentity,
|
|
375
|
-
OpenedWebIdentity,
|
|
376
|
-
WebIdentityRecoveryMaterial,
|
|
377
|
-
WebIdentityUnlockFailure,
|
|
378
|
-
WrapInput,
|
|
379
|
-
} from './crypto/webIdentityCarrier';
|
|
347
|
+
// Identity proofs — the one signed format for operations on a personal root
|
|
348
|
+
// (docs/adr/0024-one-oxy-account-root-holders.md D7)
|
|
380
349
|
export { digestIdentityPayload, signIdentityProof } from './crypto/identityProof';
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
createMoveCommitment,
|
|
384
|
-
deriveMoveKey,
|
|
385
|
-
deriveMoveSas,
|
|
386
|
-
digestMoveCiphertext,
|
|
387
|
-
generateMoveEphemeralKeyPair,
|
|
388
|
-
openMovedIdentity,
|
|
389
|
-
parseMoveQrPayload,
|
|
390
|
-
sealIdentityForMove,
|
|
391
|
-
signMoveReceipt,
|
|
392
|
-
verifyMoveCommitment,
|
|
393
|
-
verifyMoveReceipt,
|
|
394
|
-
} from './crypto/identityMove';
|
|
395
|
-
export type { MoveReceiptClaims } from './crypto/identityMove';
|
|
350
|
+
// Linking Commons to a passkey account: the code both screens compare (ADR 0029 D3)
|
|
351
|
+
export { deriveIdentityLinkCode } from './crypto/identityLink';
|
|
396
352
|
|
|
397
353
|
// ---------------------------------------------------------------------------
|
|
398
354
|
// Devices
|
|
@@ -411,6 +367,8 @@ export type {
|
|
|
411
367
|
User,
|
|
412
368
|
LoginResponse,
|
|
413
369
|
Notification,
|
|
370
|
+
NotificationActor,
|
|
371
|
+
NotificationPage,
|
|
414
372
|
Wallet,
|
|
415
373
|
Transaction,
|
|
416
374
|
BlockedUser,
|
|
@@ -9,7 +9,15 @@ import type {
|
|
|
9
9
|
LoginResult,
|
|
10
10
|
CommonsDenyReason,
|
|
11
11
|
} from '@oxy.so/contracts';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
emailVerificationConfirmResponseSchema,
|
|
14
|
+
emailVerificationStartResponseSchema,
|
|
15
|
+
loginResultSchema,
|
|
16
|
+
safeParseContract,
|
|
17
|
+
type EmailVerificationConfirmResponse,
|
|
18
|
+
type EmailVerificationStartRequest,
|
|
19
|
+
type EmailVerificationStartResponse,
|
|
20
|
+
} from '@oxy.so/contracts';
|
|
13
21
|
import type { SessionLoginResponse } from '../models/session';
|
|
14
22
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
15
23
|
import type { PublicApplication } from './OxyServices.connectedApps';
|
|
@@ -463,6 +471,12 @@ export class ServiceCredentialMismatchError extends Error {
|
|
|
463
471
|
}
|
|
464
472
|
}
|
|
465
473
|
|
|
474
|
+
/**
|
|
475
|
+
* How long a failed service-token mint sends session-less requests anonymous
|
|
476
|
+
* before the next attempt, under `serviceIdentity: 'when-anonymous'`.
|
|
477
|
+
*/
|
|
478
|
+
export const ANONYMOUS_SERVICE_TOKEN_RETRY_MS = 30_000;
|
|
479
|
+
|
|
466
480
|
export function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T) {
|
|
467
481
|
return class extends Base {
|
|
468
482
|
/**
|
|
@@ -489,8 +503,57 @@ export function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T)
|
|
|
489
503
|
/** @internal Raw apiSecret stored by configureServiceAuth() for use by getServiceToken() */
|
|
490
504
|
_serviceApiSecret: string | null = null;
|
|
491
505
|
|
|
506
|
+
/**
|
|
507
|
+
* Before this instant, a request without a user session does not try to
|
|
508
|
+
* mint a service token and goes out anonymous. Set after a failed mint; see
|
|
509
|
+
* {@link _serviceTokenForAnonymousRequest}.
|
|
510
|
+
*
|
|
511
|
+
* @internal
|
|
512
|
+
*/
|
|
513
|
+
_anonymousServiceTokenRetryAt = 0;
|
|
514
|
+
|
|
492
515
|
constructor(...args: any[]) {
|
|
493
516
|
super(...(args as [any]));
|
|
517
|
+
if (this.config.serviceIdentity === 'when-anonymous') {
|
|
518
|
+
this.httpService.setAnonymousAuthProvider(() => this._serviceTokenForAnonymousRequest());
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* The bearer for a request with no user session, under `serviceIdentity:
|
|
524
|
+
* 'when-anonymous'`: this process's service token, or `null` to send the
|
|
525
|
+
* request anonymous as before.
|
|
526
|
+
*
|
|
527
|
+
* `null`, never a throw, whenever there is no token to offer:
|
|
528
|
+
* - no key pair and no attestable workload (a local checkout) — asking
|
|
529
|
+
* costs nothing and there is nothing to ask for;
|
|
530
|
+
* - the mint failed. The request still goes out, anonymous, and so does
|
|
531
|
+
* every request for the next {@link ANONYMOUS_SERVICE_TOKEN_RETRY_MS}, so
|
|
532
|
+
* an Oxy that is refusing attestation (seen for ~2.5 minutes after a
|
|
533
|
+
* deploy on 2026-09-25) is asked once per window rather than once per
|
|
534
|
+
* read, and no read waits on a mint that just failed.
|
|
535
|
+
*
|
|
536
|
+
* The token itself comes from {@link getServiceToken}: cached for its
|
|
537
|
+
* lifetime and single-flight, so concurrent reads share one mint.
|
|
538
|
+
*
|
|
539
|
+
* @internal
|
|
540
|
+
*/
|
|
541
|
+
async _serviceTokenForAnonymousRequest(): Promise<string | null> {
|
|
542
|
+
if (Date.now() < this._anonymousServiceTokenRetryAt) return null;
|
|
543
|
+
const hasKeyPair = Boolean(this._serviceApiKey && this._serviceApiSecret);
|
|
544
|
+
if (!hasKeyPair && !(await this._canUseWorkloadIdentity())) return null;
|
|
545
|
+
try {
|
|
546
|
+
return await this.getServiceToken();
|
|
547
|
+
} catch (error) {
|
|
548
|
+
this._anonymousServiceTokenRetryAt = Date.now() + ANONYMOUS_SERVICE_TOKEN_RETRY_MS;
|
|
549
|
+
logger.warn('[oxy.auth] No service token for a request without a session; sending it anonymous', {
|
|
550
|
+
component: 'auth',
|
|
551
|
+
method: '_serviceTokenForAnonymousRequest',
|
|
552
|
+
retryInMs: ANONYMOUS_SERVICE_TOKEN_RETRY_MS,
|
|
553
|
+
error: error instanceof Error ? error.message : String(error),
|
|
554
|
+
});
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
494
557
|
}
|
|
495
558
|
|
|
496
559
|
/**
|
|
@@ -1692,12 +1755,43 @@ export function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T)
|
|
|
1692
1755
|
}
|
|
1693
1756
|
|
|
1694
1757
|
/**
|
|
1695
|
-
*
|
|
1758
|
+
* Send a 6-digit code to an email (ADR 0029 D3): the recovery email of a
|
|
1759
|
+
* new passkey account (`purpose: 'signup'`), or — named by username or
|
|
1760
|
+
* email — the recovery email of an account being recovered
|
|
1761
|
+
* (`purpose: 'recovery'`). The answer is the same whether or not an
|
|
1762
|
+
* account exists; nothing is revealed about who has one.
|
|
1696
1763
|
*/
|
|
1697
|
-
async
|
|
1764
|
+
async startEmailVerification(request: EmailVerificationStartRequest): Promise<EmailVerificationStartResponse> {
|
|
1698
1765
|
try {
|
|
1699
|
-
|
|
1700
|
-
|
|
1766
|
+
const res = await this.makeRequest<unknown>('POST', '/auth/email/verify/start', request, {
|
|
1767
|
+
cache: false,
|
|
1768
|
+
skipAuth: true,
|
|
1769
|
+
});
|
|
1770
|
+
const parsed = safeParseContract(emailVerificationStartResponseSchema, res);
|
|
1771
|
+
if (!parsed) throw new Error('auth/email/verify/start returned an unexpected response shape');
|
|
1772
|
+
return parsed;
|
|
1773
|
+
} catch (error) {
|
|
1774
|
+
throw this.handleError(error);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
/**
|
|
1779
|
+
* Confirm the code {@link startEmailVerification} sent. Resolves to a
|
|
1780
|
+
* short-lived one-use ticket: a sign-up passes it (with the email) to
|
|
1781
|
+
* {@link webauthnRegisterVerify}; a recovery passes it as `recoveryTicket`
|
|
1782
|
+
* to {@link webauthnRegisterOptions} and {@link webauthnRegisterVerify}.
|
|
1783
|
+
*/
|
|
1784
|
+
async confirmEmailVerification(verificationId: string, code: string): Promise<EmailVerificationConfirmResponse> {
|
|
1785
|
+
try {
|
|
1786
|
+
const res = await this.makeRequest<unknown>(
|
|
1787
|
+
'POST',
|
|
1788
|
+
'/auth/email/verify/confirm',
|
|
1789
|
+
{ verificationId, code },
|
|
1790
|
+
{ cache: false, skipAuth: true },
|
|
1791
|
+
);
|
|
1792
|
+
const parsed = safeParseContract(emailVerificationConfirmResponseSchema, res);
|
|
1793
|
+
if (!parsed) throw new Error('auth/email/verify/confirm returned an unexpected response shape');
|
|
1794
|
+
return parsed;
|
|
1701
1795
|
} catch (error) {
|
|
1702
1796
|
throw this.handleError(error);
|
|
1703
1797
|
}
|
|
@@ -1708,19 +1802,24 @@ export function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T)
|
|
|
1708
1802
|
* `PublicKeyCredentialCreationOptions` the browser's `navigator.credentials
|
|
1709
1803
|
* .create()` (or `@simplewebauthn/browser`'s `startRegistration`) needs.
|
|
1710
1804
|
*
|
|
1711
|
-
* With a bearer token planted this
|
|
1712
|
-
*
|
|
1713
|
-
*
|
|
1714
|
-
*
|
|
1715
|
-
* as `unknown` for the caller
|
|
1805
|
+
* With a bearer token planted this adds a passkey to the signed-in account.
|
|
1806
|
+
* Without one, `username` is a prospective sign-up's handle, and
|
|
1807
|
+
* `recoveryTicket` a recovery's new passkey for the account it names. The
|
|
1808
|
+
* returned options are OPAQUE — Oxy does not own their shape (the browser /
|
|
1809
|
+
* `@simplewebauthn` does), so they pass through as `unknown` for the caller
|
|
1810
|
+
* to hand straight to the ceremony.
|
|
1716
1811
|
*/
|
|
1717
|
-
async webauthnRegisterOptions(username?: string): Promise<unknown> {
|
|
1812
|
+
async webauthnRegisterOptions(request: { username?: string; recoveryTicket?: string } = {}): Promise<unknown> {
|
|
1813
|
+
const signedOut = request.username !== undefined || request.recoveryTicket !== undefined;
|
|
1718
1814
|
try {
|
|
1719
1815
|
return await this.makeRequest<unknown>(
|
|
1720
1816
|
'POST',
|
|
1721
1817
|
'/auth/webauthn/register/options',
|
|
1722
|
-
{
|
|
1723
|
-
|
|
1818
|
+
{
|
|
1819
|
+
...(request.username !== undefined ? { username: request.username } : {}),
|
|
1820
|
+
...(request.recoveryTicket !== undefined ? { recoveryTicket: request.recoveryTicket } : {}),
|
|
1821
|
+
},
|
|
1822
|
+
{ cache: false, ...(signedOut ? { skipAuth: true } : {}) },
|
|
1724
1823
|
);
|
|
1725
1824
|
} catch (error) {
|
|
1726
1825
|
throw this.handleError(error);
|
|
@@ -1732,10 +1831,12 @@ export function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T)
|
|
|
1732
1831
|
* browser `RegistrationResponseJSON` (`response`) alongside the Oxy envelope
|
|
1733
1832
|
* (desired `username` for signup + the device-session naming fields).
|
|
1734
1833
|
*
|
|
1735
|
-
*
|
|
1736
|
-
* - **
|
|
1737
|
-
*
|
|
1738
|
-
*
|
|
1834
|
+
* Server branches, disambiguated by the response shape:
|
|
1835
|
+
* - **Sign-up** (no bearer; `username`, `email`, `emailTicket`) and
|
|
1836
|
+
* **recovery** (no bearer; `recoveryTicket`): the account is created, or
|
|
1837
|
+
* gains the passkey, and a session is minted — the response carries
|
|
1838
|
+
* `sessionId`, is the SAME {@link LoginResult} contract as
|
|
1839
|
+
* `POST /auth/verify`, and its access token is planted here.
|
|
1739
1840
|
* - **Link** (bearer present): the passkey is attached to the signed-in
|
|
1740
1841
|
* account and the server returns `{ success, message }` with no session,
|
|
1741
1842
|
* which is returned verbatim (no token planting).
|
|
@@ -1744,14 +1845,13 @@ export function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T)
|
|
|
1744
1845
|
response: unknown,
|
|
1745
1846
|
envelope: {
|
|
1746
1847
|
username?: string;
|
|
1848
|
+
/** Sign-up: the recovery email, confirmed with `emailTicket`. */
|
|
1849
|
+
email?: string;
|
|
1850
|
+
emailTicket?: string;
|
|
1851
|
+
/** Recovery: the ticket {@link confirmEmailVerification} returned. */
|
|
1852
|
+
recoveryTicket?: string;
|
|
1747
1853
|
deviceName?: string;
|
|
1748
1854
|
deviceFingerprint?: string;
|
|
1749
|
-
/**
|
|
1750
|
-
* Sign-up only: the root created on the holder before this call, sealed
|
|
1751
|
-
* under the passkey being registered, plus its `enroll_identity` proof
|
|
1752
|
-
* (ADR 0024 D4). The account is created WITH it or not at all.
|
|
1753
|
-
*/
|
|
1754
|
-
identity?: { envelope: WebIdentityEnvelope; proof: IdentityProof };
|
|
1755
1855
|
} = {},
|
|
1756
1856
|
): Promise<{ success: true; message: string } | LoginResult> {
|
|
1757
1857
|
try {
|
|
@@ -1759,7 +1859,10 @@ export function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T)
|
|
|
1759
1859
|
'POST',
|
|
1760
1860
|
'/auth/webauthn/register/verify',
|
|
1761
1861
|
{ response, ...envelope },
|
|
1762
|
-
{
|
|
1862
|
+
{
|
|
1863
|
+
cache: false,
|
|
1864
|
+
...(envelope.username !== undefined || envelope.recoveryTicket !== undefined ? { skipAuth: true } : {}),
|
|
1865
|
+
},
|
|
1763
1866
|
);
|
|
1764
1867
|
if (res && typeof res === 'object') {
|
|
1765
1868
|
const record = res as Record<string, unknown>;
|