@things-factory/auth-base 10.1.213 → 10.1.218
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/config/config.development.js +2 -4
- package/config/config.production.js +1 -2
- package/dist-client/actions/auth.d.ts +1 -0
- package/dist-client/actions/auth.js +6 -0
- package/dist-client/actions/auth.js.map +1 -1
- package/dist-client/reducers/auth.d.ts +8 -0
- package/dist-client/reducers/auth.js +8 -2
- package/dist-client/reducers/auth.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/constants/error-code.d.ts +1 -0
- package/dist-server/constants/error-code.js +2 -1
- package/dist-server/constants/error-code.js.map +1 -1
- package/dist-server/controllers/auth.d.ts +0 -1
- package/dist-server/controllers/auth.js +0 -1
- package/dist-server/controllers/auth.js.map +1 -1
- package/dist-server/controllers/password-setup.d.ts +33 -0
- package/dist-server/controllers/password-setup.js +77 -0
- package/dist-server/controllers/password-setup.js.map +1 -0
- package/dist-server/controllers/reset-password.d.ts +8 -1
- package/dist-server/controllers/reset-password.js +21 -11
- package/dist-server/controllers/reset-password.js.map +1 -1
- package/dist-server/controllers/signin.js +10 -0
- package/dist-server/controllers/signin.js.map +1 -1
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +2 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/middlewares/jwt-authenticate-middleware.js +8 -25
- package/dist-server/middlewares/jwt-authenticate-middleware.js.map +1 -1
- package/dist-server/router/auth-public-process-router.js +3 -41
- package/dist-server/router/auth-public-process-router.js.map +1 -1
- package/dist-server/service/access-request/access-request.js +2 -2
- package/dist-server/service/access-request/access-request.js.map +1 -1
- package/dist-server/service/audit/audit-kinds.js +1 -0
- package/dist-server/service/audit/audit-kinds.js.map +1 -1
- package/dist-server/service/audit/audit-subscriber.d.ts +1 -7
- package/dist-server/service/audit/audit-subscriber.js +2 -33
- package/dist-server/service/audit/audit-subscriber.js.map +1 -1
- package/dist-server/service/audit/index.d.ts +1 -1
- package/dist-server/service/index.d.ts +2 -4
- package/dist-server/service/index.js +22 -27
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/open-fields.js +0 -2
- package/dist-server/service/open-fields.js.map +1 -1
- package/dist-server/service/user/user-mutation.d.ts +27 -6
- package/dist-server/service/user/user-mutation.js +32 -114
- package/dist-server/service/user/user-mutation.js.map +1 -1
- package/dist-server/service/user/user-query.d.ts +3 -2
- package/dist-server/service/user/user-query.js +17 -21
- package/dist-server/service/user/user-query.js.map +1 -1
- package/dist-server/service/user/user-types.d.ts +6 -2
- package/dist-server/service/user/user-types.js +20 -9
- package/dist-server/service/user/user-types.js.map +1 -1
- package/dist-server/service/user/user.js +17 -8
- package/dist-server/service/user/user.js.map +1 -1
- package/dist-server/service/verification-token/verification-token.d.ts +1 -0
- package/dist-server/service/verification-token/verification-token.js +1 -0
- package/dist-server/service/verification-token/verification-token.js.map +1 -1
- package/dist-server/templates/reset-password-email.d.ts +10 -3
- package/dist-server/templates/reset-password-email.js +8 -4
- package/dist-server/templates/reset-password-email.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/utils/password-reset-rule.d.ts +15 -0
- package/dist-server/utils/password-reset-rule.js +34 -0
- package/dist-server/utils/password-reset-rule.js.map +1 -0
- package/dist-server/utils/verification-token-rule.js +3 -1
- package/dist-server/utils/verification-token-rule.js.map +1 -1
- package/package.json +2 -2
- package/tests/add-user-gates.test.ts +34 -0
- package/tests/list-inheritance-db.test.ts +0 -15
- package/tests/lock-recovery.test.ts +2 -2
- package/tests/open-doors.test.ts +2 -5
- package/tests/password-reset-rule-db.test.ts +159 -0
- package/tests/password-setup-db.test.ts +213 -0
- package/tests/picture-reducer.test.ts +25 -0
- package/tests/verification-token-db.test.ts +12 -7
- package/translations/en.json +4 -2
- package/translations/es.json +4 -2
- package/translations/ja.json +4 -2
- package/translations/ko.json +4 -2
- package/translations/ms.json +4 -2
- package/translations/zh.json +4 -2
- package/dist-server/controllers/invitation.d.ts +0 -75
- package/dist-server/controllers/invitation.js +0 -195
- package/dist-server/controllers/invitation.js.map +0 -1
- package/dist-server/controllers/utils/make-invitation-token.d.ts +0 -1
- package/dist-server/controllers/utils/make-invitation-token.js +0 -9
- package/dist-server/controllers/utils/make-invitation-token.js.map +0 -1
- package/dist-server/controllers/utils/save-invitation-token.d.ts +0 -5
- package/dist-server/controllers/utils/save-invitation-token.js +0 -12
- package/dist-server/controllers/utils/save-invitation-token.js.map +0 -1
- package/dist-server/service/invitation/index.d.ts +0 -5
- package/dist-server/service/invitation/index.js +0 -9
- package/dist-server/service/invitation/index.js.map +0 -1
- package/dist-server/service/invitation/invitation-mutation.d.ts +0 -20
- package/dist-server/service/invitation/invitation-mutation.js +0 -109
- package/dist-server/service/invitation/invitation-mutation.js.map +0 -1
- package/dist-server/service/invitation/invitation-query.d.ts +0 -23
- package/dist-server/service/invitation/invitation-query.js +0 -89
- package/dist-server/service/invitation/invitation-query.js.map +0 -1
- package/dist-server/service/invitation/invitation-types.d.ts +0 -5
- package/dist-server/service/invitation/invitation-types.js +0 -21
- package/dist-server/service/invitation/invitation-types.js.map +0 -1
- package/dist-server/service/invitation/invitation.d.ts +0 -26
- package/dist-server/service/invitation/invitation.js +0 -142
- package/dist-server/service/invitation/invitation.js.map +0 -1
- package/dist-server/templates/invitation-email.d.ts +0 -6
- package/dist-server/templates/invitation-email.js +0 -82
- package/dist-server/templates/invitation-email.js.map +0 -1
- package/dist-server/utils/invitation-state.d.ts +0 -52
- package/dist-server/utils/invitation-state.js +0 -76
- package/dist-server/utils/invitation-state.js.map +0 -1
- package/tests/invitation-db.test.ts +0 -416
- package/tests/invitation-route.test.ts +0 -116
- package/tests/invitation-state.test.ts +0 -92
|
@@ -109,7 +109,12 @@ describe('the rule', () => {
|
|
|
109
109
|
const now = new Date('2026-09-24T12:00:00Z')
|
|
110
110
|
|
|
111
111
|
it('gives each mailed link its lifetime', () => {
|
|
112
|
-
expect(VERIFICATION_TOKEN_LIFETIME_MS).toEqual({
|
|
112
|
+
expect(VERIFICATION_TOKEN_LIFETIME_MS).toEqual({
|
|
113
|
+
'password-reset': HOUR,
|
|
114
|
+
'password-setup': 48 * HOUR,
|
|
115
|
+
unlock: HOUR,
|
|
116
|
+
activation: 48 * HOUR
|
|
117
|
+
})
|
|
113
118
|
expect(verificationTokenExpiresAt('password-reset', now).getTime()).toBe(now.getTime() + HOUR)
|
|
114
119
|
})
|
|
115
120
|
|
|
@@ -153,11 +158,11 @@ describe('password reset', () => {
|
|
|
153
158
|
await sendPasswordResetEmail({ user, context })
|
|
154
159
|
const token = tokenInLastMail(RESET_PASSWORD_PATH)
|
|
155
160
|
|
|
156
|
-
await resetPassword(token, NEW, context)
|
|
161
|
+
await resetPassword(token, NEW, 'kim@example.com', context)
|
|
157
162
|
|
|
158
163
|
const after = await getRepository(User).findOneBy({ id: user.id })
|
|
159
164
|
expect(User.verify(after.password, NEW, after.salt)).toBe(true)
|
|
160
|
-
await expect(resetPassword(token, OLD, context)).rejects.toThrow('text.invalid verification token')
|
|
165
|
+
await expect(resetPassword(token, OLD, 'kim@example.com', context)).rejects.toThrow('text.invalid verification token')
|
|
161
166
|
})
|
|
162
167
|
|
|
163
168
|
it('refuses the link after it expires', async () => {
|
|
@@ -167,7 +172,7 @@ describe('password reset', () => {
|
|
|
167
172
|
|
|
168
173
|
await expireAt(user.id, new Date(Date.now() - 1))
|
|
169
174
|
|
|
170
|
-
await expect(resetPassword(token, NEW, context)).rejects.toThrow('text.invalid verification token')
|
|
175
|
+
await expect(resetPassword(token, NEW, 'kim@example.com', context)).rejects.toThrow('text.invalid verification token')
|
|
171
176
|
const after = await getRepository(User).findOneBy({ id: user.id })
|
|
172
177
|
expect(User.verify(after.password, OLD, after.salt)).toBe(true)
|
|
173
178
|
})
|
|
@@ -180,7 +185,7 @@ describe('password reset', () => {
|
|
|
180
185
|
type: VerificationTokenType.PASSWORD_RESET
|
|
181
186
|
})
|
|
182
187
|
|
|
183
|
-
await expect(resetPassword('b'.repeat(32), NEW, context)).rejects.toThrow('text.invalid verification token')
|
|
188
|
+
await expect(resetPassword('b'.repeat(32), NEW, 'kim@example.com', context)).rejects.toThrow('text.invalid verification token')
|
|
184
189
|
})
|
|
185
190
|
|
|
186
191
|
it('refuses a row whose expiry is missing, even when the hash matches', async () => {
|
|
@@ -194,7 +199,7 @@ describe('password reset', () => {
|
|
|
194
199
|
|
|
195
200
|
await expireAt(user.id, null)
|
|
196
201
|
|
|
197
|
-
await expect(resetPassword(token, NEW, context)).rejects.toThrow('text.invalid verification token')
|
|
202
|
+
await expect(resetPassword(token, NEW, 'kim@example.com', context)).rejects.toThrow('text.invalid verification token')
|
|
198
203
|
})
|
|
199
204
|
|
|
200
205
|
it('does not find a token by the value stored in the table', async () => {
|
|
@@ -203,7 +208,7 @@ describe('password reset', () => {
|
|
|
203
208
|
await sendPasswordResetEmail({ user, context })
|
|
204
209
|
const stored = (await row(user.id)).token
|
|
205
210
|
|
|
206
|
-
await expect(resetPassword(stored, NEW, context)).rejects.toThrow('text.invalid verification token')
|
|
211
|
+
await expect(resetPassword(stored, NEW, 'kim@example.com', context)).rejects.toThrow('text.invalid verification token')
|
|
207
212
|
})
|
|
208
213
|
})
|
|
209
214
|
|
package/translations/en.json
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"error.invalid username": "invalid username: {username}",
|
|
10
10
|
"error.mail could not be sent": "the email could not be sent. try again in a moment.",
|
|
11
11
|
"error.ownership required": "unauthorized! domain or system ownership required",
|
|
12
|
-
"error.password should be supported": "initial password or default password should be supported",
|
|
13
12
|
"error.password should match the rule": "password should match following rule. ${rule}",
|
|
14
13
|
"error.password used in the past": "password used in the past",
|
|
15
14
|
"error.role-name-stands-inherited": "a role called {name} is already visible here, defined in {domain}. stand this domain's own of that name only if that is what you mean to do.",
|
|
@@ -29,6 +28,10 @@
|
|
|
29
28
|
"error.x is not a member of y": "{x} is not a member of {y}",
|
|
30
29
|
"error.x privilege required": "unauthorized! {x} privilege required",
|
|
31
30
|
"error.role-not-assignable-here": "this domain cannot assign the role(s) {roles}.",
|
|
31
|
+
"error.password-setup-pending": "This account is waiting for its password. Set it through the password setup link you received, then sign in.",
|
|
32
|
+
"error.password-setup-mail-not-sent": "The mail to {email} did not go out. Nothing was changed - copy the link instead.",
|
|
33
|
+
"error.password-reset-owner-account": "A domain owner's or administrator's password is not reset to the default.",
|
|
34
|
+
"error.password-reset-reaches-other-domains": "they also work in {domains}, so their password is not this $t(term.domain)'s to reset.",
|
|
32
35
|
"error.x not allowed from this address": "unauthorized! {x} is not allowed from this address",
|
|
33
36
|
"error.x not licensed": "unauthorized! {x} is not licensed on this installation",
|
|
34
37
|
"error.x refused for this request": "unauthorized! {x} is refused for this request",
|
|
@@ -210,7 +213,6 @@
|
|
|
210
213
|
"text.delete account succeed": "delete account succeed",
|
|
211
214
|
"text.inactive user": "inactive user",
|
|
212
215
|
"text.invalid verification token": "invalid verification token",
|
|
213
|
-
"text.invitation email sent": "invitation email sent",
|
|
214
216
|
"text.password changed successfully": "password changed successfully",
|
|
215
217
|
"text.password reset email sent": "if an account uses this address, a password reset email is on its way.",
|
|
216
218
|
"text.password reset succeed": "password reset succeed",
|
package/translations/es.json
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"error.invalid username": "nombre de usuario no válido: {username}",
|
|
10
10
|
"error.mail could not be sent": "no se pudo enviar el correo. vuelva a intentarlo en un momento.",
|
|
11
11
|
"error.ownership required": "¡no autorizado! se requiere la propiedad del dominio o del sistema",
|
|
12
|
-
"error.password should be supported": "se debe proporcionar una contraseña inicial o una contraseña predeterminada",
|
|
13
12
|
"error.password should match the rule": "la contraseña debe cumplir la siguiente regla. ${rule}",
|
|
14
13
|
"error.password used in the past": "contraseña usada anteriormente",
|
|
15
14
|
"error.role-name-stands-inherited": "ya se muestra aquí un rol llamado {name}, definido en {domain}. cree en este dominio uno propio con ese nombre solo si es lo que pretende hacer.",
|
|
@@ -29,6 +28,10 @@
|
|
|
29
28
|
"error.x is not a member of y": "{x} no es miembro de {y}",
|
|
30
29
|
"error.x privilege required": "¡no autorizado! se requiere el privilegio {x}",
|
|
31
30
|
"error.role-not-assignable-here": "este dominio no puede asignar los roles {roles}.",
|
|
31
|
+
"error.password-setup-pending": "Esta cuenta espera su contraseña. Establézcala con el enlace de configuración recibido y luego inicie sesión.",
|
|
32
|
+
"error.password-setup-mail-not-sent": "No se pudo enviar el correo a {email}. No se cambió nada: copie el enlace.",
|
|
33
|
+
"error.password-reset-owner-account": "la contraseña de un propietario o administrador no se restablece al valor predeterminado.",
|
|
34
|
+
"error.password-reset-reaches-other-domains": "también trabaja en {domains}; este dominio no puede restablecer su contraseña.",
|
|
32
35
|
"error.x not allowed from this address": "¡no autorizado! {x} no está permitido desde esta dirección",
|
|
33
36
|
"error.x not licensed": "¡no autorizado! {x} no tiene licencia en esta instalación",
|
|
34
37
|
"error.x refused for this request": "¡no autorizado! {x} se ha denegado para esta solicitud",
|
|
@@ -210,7 +213,6 @@
|
|
|
210
213
|
"text.delete account succeed": "cuenta eliminada correctamente",
|
|
211
214
|
"text.inactive user": "usuario inactivo",
|
|
212
215
|
"text.invalid verification token": "token de verificación no válido",
|
|
213
|
-
"text.invitation email sent": "correo de invitación enviado",
|
|
214
216
|
"text.password changed successfully": "contraseña cambiada correctamente",
|
|
215
217
|
"text.password reset email sent": "si hay una cuenta con esta dirección, le enviamos un correo de restablecimiento de contraseña.",
|
|
216
218
|
"text.password reset succeed": "contraseña restablecida correctamente",
|
package/translations/ja.json
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"error.invalid username": "無効なユーザー名: {username}",
|
|
10
10
|
"error.mail could not be sent": "メールを送れませんでした. しばらくしてからもう一度お試しください.",
|
|
11
11
|
"error.ownership required": "権限がありません。$t(term.domain)またはシステムの所有者権限が必要です。",
|
|
12
|
-
"error.password should be supported": "初期パスワードまたはデフォルトパスワードがサポートされるべきです",
|
|
13
12
|
"error.password should match the rule": "パスワードは次の規則を守らなければなりません. {rule}",
|
|
14
13
|
"error.password used in the past": "過去に使用されたパスワードです.",
|
|
15
14
|
"error.role-name-stands-inherited": "{name} ロールは {domain} に定義されており、ここからも見えています。この$t(term.domain)に同じ名前で別に立てるつもりの場合のみ作成します。",
|
|
@@ -29,6 +28,10 @@
|
|
|
29
28
|
"error.x is not a member of y": "{x}は{y}のメンバーではありません.",
|
|
30
29
|
"error.x privilege required": "権限がありません。{x} 権限が必要です。",
|
|
31
30
|
"error.role-not-assignable-here": "この$t(term.domain)では付与できないロールです: {roles}",
|
|
31
|
+
"error.password-setup-pending": "パスワード設定待ちのアカウントです。受け取ったパスワード設定リンクでパスワードを設定してからログインしてください。",
|
|
32
|
+
"error.password-setup-mail-not-sent": "{email} にメールを送れませんでした。変更はありません。リンクをコピーして伝えてください。",
|
|
33
|
+
"error.password-reset-owner-account": "$t(term.domain)のオーナーや管理者のパスワードは初期値にリセットしません。",
|
|
34
|
+
"error.password-reset-reaches-other-domains": "{domains}でも利用している方のため、この$t(term.domain)ではパスワードをリセットできません。",
|
|
32
35
|
"error.x not allowed from this address": "権限がありません。このアドレスからは {x} 権限を使えません。",
|
|
33
36
|
"error.x not licensed": "権限がありません。このインストールのライセンスには {x} 権限がありません。",
|
|
34
37
|
"error.x refused for this request": "権限がありません。このリクエストでは {x} 権限が許可されていません。",
|
|
@@ -50,7 +53,6 @@
|
|
|
50
53
|
"text.delete account succeed": "アカウントが削除されました.",
|
|
51
54
|
"text.inactive user": "非アクティブユーザーです.",
|
|
52
55
|
"text.invalid verification token": "確認トークンが無効です.",
|
|
53
|
-
"text.invitation email sent": "招待メールを送りました.",
|
|
54
56
|
"text.password changed successfully": "パスワードが正常に変更されました.",
|
|
55
57
|
"text.password reset email sent": "登録済みのアドレスであれば、パスワード リセットメールを送りました.",
|
|
56
58
|
"text.password reset succeed": "パスワードが正常にリセットされました.",
|
package/translations/ko.json
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"error.invalid username": "유효하지 않은 사용자 이름: {username}",
|
|
10
10
|
"error.mail could not be sent": "메일을 보내지 못했습니다. 잠시 후 다시 시도하십시오.",
|
|
11
11
|
"error.ownership required": "권한이 없습니다. $t(term.domain) 또는 시스템 소유자 권한이 필요합니다.",
|
|
12
|
-
"error.password should be supported": "초기 비밀번호나 디폴트 비밀번호가 제공되어야 합니다.",
|
|
13
12
|
"error.password should match the rule": "비밀번호는 다음 규칙을 지켜야 합니다. {rule}",
|
|
14
13
|
"error.password used in the past": "과거에 사용된 비밀번호입니다.",
|
|
15
14
|
"error.role-name-stands-inherited": "{name} 역할은 {domain}에 정의되어 현재 도메인에 상속되어 있습니다. 상속된 역할을 대체하려는 경우에만 이 $t(term.domain)에 같은 이름의 역할을 생성해 주세요.",
|
|
@@ -29,6 +28,10 @@
|
|
|
29
28
|
"error.x is not a member of y": "{x}은(는) {y}의 멤버가 아닙니다.",
|
|
30
29
|
"error.x privilege required": "권한이 없습니다. {x} 권한이 필요합니다.",
|
|
31
30
|
"error.role-not-assignable-here": "이 $t(term.domain)에서 줄 수 없는 역할입니다: {roles}",
|
|
31
|
+
"error.password-setup-pending": "비밀번호 설정을 기다리는 계정입니다. 받은 비밀번호 설정 링크에서 비밀번호를 정한 뒤 로그인하세요.",
|
|
32
|
+
"error.password-setup-mail-not-sent": "{email}(으)로 메일을 보내지 못했습니다. 바뀐 것은 없습니다 — 링크 복사로 전하세요.",
|
|
33
|
+
"error.password-reset-owner-account": "$t(term.domain) 오너나 관리자의 비밀번호는 기본값으로 초기화하지 않습니다.",
|
|
34
|
+
"error.password-reset-reaches-other-domains": "{domains}에서도 일하는 사람이라 이 $t(term.domain)에서 비밀번호를 초기화할 수 없습니다.",
|
|
32
35
|
"error.x not allowed from this address": "권한이 없습니다. 이 주소에서는 {x} 권한을 사용할 수 없습니다.",
|
|
33
36
|
"error.x not licensed": "권한이 없습니다. 이 설치의 라이선스에는 {x} 권한이 없습니다.",
|
|
34
37
|
"error.x refused for this request": "권한이 없습니다. 이 요청에는 {x} 권한이 허용되지 않습니다.",
|
|
@@ -210,7 +213,6 @@
|
|
|
210
213
|
"text.delete account succeed": "계정이 삭제되었습니다.",
|
|
211
214
|
"text.inactive user": "비활성 사용자입니다.",
|
|
212
215
|
"text.invalid verification token": "확인 토큰이 유효하지 않습니다.",
|
|
213
|
-
"text.invitation email sent": "초대 이메일을 보냈습니다.",
|
|
214
216
|
"text.password changed successfully": "비밀번호가 성공적으로 변경되었습니다.",
|
|
215
217
|
"text.password reset email sent": "가입된 주소라면 비밀번호 재설정 메일을 보냈습니다.",
|
|
216
218
|
"text.password reset succeed": "비밀번호 성공적으로 재설정되었습니다.",
|
package/translations/ms.json
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"error.invalid username": "Nama pengguna tidak sah: {username}",
|
|
10
10
|
"error.mail could not be sent": "E-mel tidak dapat dihantar. Cuba lagi sebentar lagi.",
|
|
11
11
|
"error.ownership required": "tidak dibenarkan! kebenaran pemilik domain atau sistem diperlukan",
|
|
12
|
-
"error.password should be supported": "kata laluan awal atau kata laluan lalai harus disokong",
|
|
13
12
|
"error.password should match the rule": "Kata laluan harus mematuhi peraturan berikut. ${rule}",
|
|
14
13
|
"error.password used in the past": "Kata laluan telah digunakan dalam masa lampau",
|
|
15
14
|
"error.role-name-stands-inherited": "peranan bernama {name} ditakrifkan dalam {domain} dan kelihatan di sini juga. Bina milik domain ini dengan nama yang sama hanya jika itu memang yang dimaksudkan.",
|
|
@@ -29,6 +28,10 @@
|
|
|
29
28
|
"error.x is not a member of y": "{x} bukan ahli {y}",
|
|
30
29
|
"error.x privilege required": "tidak dibenarkan! kebenaran {x} diperlukan",
|
|
31
30
|
"error.role-not-assignable-here": "$t(term.domain) ini tidak boleh memberikan peranan: {roles}",
|
|
31
|
+
"error.password-setup-pending": "Akaun ini menunggu kata laluannya. Tetapkannya melalui pautan tetapan kata laluan yang diterima, kemudian log masuk.",
|
|
32
|
+
"error.password-setup-mail-not-sent": "E-mel kepada {email} tidak dihantar. Tiada apa yang diubah - salin pautan sebagai ganti.",
|
|
33
|
+
"error.password-reset-owner-account": "Kata laluan pemilik $t(term.domain) atau pentadbir tidak ditetapkan semula kepada lalai.",
|
|
34
|
+
"error.password-reset-reaches-other-domains": "Pengguna ini juga bekerja di {domains}, jadi $t(term.domain) ini tidak boleh menetapkan semula kata laluannya.",
|
|
32
35
|
"error.x not allowed from this address": "tidak dibenarkan! kebenaran {x} tidak dibenarkan dari alamat ini",
|
|
33
36
|
"error.x not licensed": "tidak dibenarkan! kebenaran {x} tiada dalam lesen pemasangan ini",
|
|
34
37
|
"error.x refused for this request": "tidak dibenarkan! kebenaran {x} ditolak untuk permintaan ini",
|
|
@@ -50,7 +53,6 @@
|
|
|
50
53
|
"text.delete account succeed": "Berjaya memadam akaun",
|
|
51
54
|
"text.inactive user": "Pengguna tidak aktif",
|
|
52
55
|
"text.invalid verification token": "Token pengesahan tidak sah",
|
|
53
|
-
"text.invitation email sent": "E-mel jemputan telah dihantar",
|
|
54
56
|
"text.password changed successfully": "Kata laluan berjaya diubah",
|
|
55
57
|
"text.password reset email sent": "Jika alamat ini didaftarkan, e-mel reset kata laluan telah dihantar",
|
|
56
58
|
"text.password reset succeed": "Reset kata laluan berjaya",
|
package/translations/zh.json
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"error.invalid username": "无效的用户名: {username}",
|
|
10
10
|
"error.mail could not be sent": "邮件未能发送。请稍后再试。",
|
|
11
11
|
"error.ownership required": "没有权限。需要$t(term.domain)或系统所有者权限。",
|
|
12
|
-
"error.password should be supported": "应支持初始密码或默认密码",
|
|
13
12
|
"error.password should match the rule": "密码应符合以下规则。${rule}",
|
|
14
13
|
"error.password used in the past": "使用过的密码!",
|
|
15
14
|
"error.role-name-stands-inherited": "名为 {name} 的角色定义在 {domain},在此处也可见。只有确实要在该$t(term.domain)中另行建立同名角色时才创建。",
|
|
@@ -29,6 +28,10 @@
|
|
|
29
28
|
"error.x is not a member of y": "{x}不是{y}的成员",
|
|
30
29
|
"error.x privilege required": "没有权限。需要 {x} 权限。",
|
|
31
30
|
"error.role-not-assignable-here": "此$t(term.domain)不能分配的角色: {roles}",
|
|
31
|
+
"error.password-setup-pending": "该账户正在等待设置密码。请通过收到的密码设置链接设置密码后登录。",
|
|
32
|
+
"error.password-setup-mail-not-sent": "未能向 {email} 发送邮件。没有任何更改,请复制链接转交。",
|
|
33
|
+
"error.password-reset-owner-account": "不会将$t(term.domain)所有者或管理员的密码重置为默认值。",
|
|
34
|
+
"error.password-reset-reaches-other-domains": "该用户也在{domains}中工作,此$t(term.domain)不能重置其密码。",
|
|
32
35
|
"error.x not allowed from this address": "没有权限。从此地址不能使用 {x} 权限。",
|
|
33
36
|
"error.x not licensed": "没有权限。此安装的许可证不包含 {x} 权限。",
|
|
34
37
|
"error.x refused for this request": "没有权限。此请求不允许 {x} 权限。",
|
|
@@ -50,7 +53,6 @@
|
|
|
50
53
|
"text.delete account succeed": "已成功删除账号。",
|
|
51
54
|
"text.inactive user": "未激活用账号。",
|
|
52
55
|
"text.invalid verification token": "令牌无效。",
|
|
53
|
-
"text.invitation email sent": "成功发送邀请电子邮件。",
|
|
54
56
|
"text.password changed successfully": "密码更改成功。",
|
|
55
57
|
"text.password reset email sent": "如果该地址已注册,我们已发送密码重置邮件。",
|
|
56
58
|
"text.password reset succeed": "密码重置成功。",
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { EntityManager } from 'typeorm';
|
|
2
|
-
import { Domain } from '@things-factory/shell';
|
|
3
|
-
import { Invitation } from '../service/invitation/invitation.js';
|
|
4
|
-
import { Role } from '../service/role/role.js';
|
|
5
|
-
import { User } from '../service/user/user.js';
|
|
6
|
-
/**
|
|
7
|
-
* Inviting somebody into a domain, and their accepting it.
|
|
8
|
-
*
|
|
9
|
-
* ── What this is for, and what it is not ────────────────────────────────────
|
|
10
|
-
* `inviteUser` already puts a person who **has an account** into a domain, at once, with no mail
|
|
11
|
-
* and no say from them. That is the right tool when the administrator has the authority and the
|
|
12
|
-
* person already exists.
|
|
13
|
-
*
|
|
14
|
-
* This is for the case the `user-invite` screen records as having no path at all: **somebody with
|
|
15
|
-
* no account, in an installation with no default password.** Nobody can set their password for
|
|
16
|
-
* them, so the only way in is a mail they can prove they received. That is what the token is.
|
|
17
|
-
*
|
|
18
|
-
* ── Where the token sits ────────────────────────────────────────────────────
|
|
19
|
-
* In the link's **fragment** — `…/accept-invitation#<token>` — which browsers do not send to the
|
|
20
|
-
* server. So it stays out of access logs, proxy logs and `Referer`. The page strips it from the
|
|
21
|
-
* address bar as soon as it has read it.
|
|
22
|
-
*
|
|
23
|
-
* That leaves one way it can still travel: the mail itself being forwarded. Nothing about where
|
|
24
|
-
* the token sits can close that, so accepting asks for the invited address as well. Holding the
|
|
25
|
-
* link is then not enough on its own.
|
|
26
|
-
*
|
|
27
|
-
* ⚠ `/verify/:token`, `/reset-password?token=` and `/unlock-user?token=` still put theirs in the
|
|
28
|
-
* URL. A password reset token is not lighter than this one. Moving those three is its own job.
|
|
29
|
-
*/
|
|
30
|
-
/** Where the accept page answers. The mail, the page and the router all read this one spelling. */
|
|
31
|
-
export declare const ACCEPT_INVITATION_PATH = "/auth/accept-invitation";
|
|
32
|
-
/**
|
|
33
|
-
* Create a pending invitation and mail it.
|
|
34
|
-
*
|
|
35
|
-
* ⚠ One pending invitation per person and domain. The history keeps every row, so this cannot be
|
|
36
|
-
* a unique index without forbidding "invited, left, invited again" — see the note on the entity.
|
|
37
|
-
* An existing pending one is re-issued rather than duplicated: a new token, a new expiry, and the
|
|
38
|
-
* same row, so the list shows one line per invitation rather than one per time somebody pressed
|
|
39
|
-
* the button.
|
|
40
|
-
*/
|
|
41
|
-
export declare function inviteByEmail({ email, domain, roles, user, context, tx }: {
|
|
42
|
-
email: string;
|
|
43
|
-
domain: Domain;
|
|
44
|
-
roles?: Role[];
|
|
45
|
-
user?: User;
|
|
46
|
-
context: any;
|
|
47
|
-
tx?: EntityManager;
|
|
48
|
-
}): Promise<Invitation>;
|
|
49
|
-
/** Mail the link. Kept apart so re-sending does not have to go through creating one. */
|
|
50
|
-
export declare function mailInvitation(invitation: Invitation, context: any): Promise<void>;
|
|
51
|
-
/**
|
|
52
|
-
* Accept an invitation.
|
|
53
|
-
*
|
|
54
|
-
* The invited address has to be given as well as the token — see the note at the top. Everything
|
|
55
|
-
* that refuses here refuses the same way on purpose: a wrong token, a wrong address, an expired
|
|
56
|
-
* one and a cancelled one are one answer, so that trying them apart tells the caller nothing.
|
|
57
|
-
*/
|
|
58
|
-
export declare function acceptInvitation({ token, email, name, password, context, tx }: {
|
|
59
|
-
token: string;
|
|
60
|
-
email: string;
|
|
61
|
-
name?: string;
|
|
62
|
-
password?: string;
|
|
63
|
-
context: any;
|
|
64
|
-
tx?: EntityManager;
|
|
65
|
-
}): Promise<{
|
|
66
|
-
domain: Domain;
|
|
67
|
-
created: boolean;
|
|
68
|
-
}>;
|
|
69
|
-
/**
|
|
70
|
-
* Withdraw a pending invitation.
|
|
71
|
-
*
|
|
72
|
-
* The row stays and says it was cancelled. Deleting it would take with it the fact that somebody
|
|
73
|
-
* was invited at all, which is the thing the list exists to show.
|
|
74
|
-
*/
|
|
75
|
-
export declare function cancelInvitation(id: string, domain: Domain, user: User, tx?: EntityManager): Promise<boolean>;
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ACCEPT_INVITATION_PATH = void 0;
|
|
4
|
-
exports.inviteByEmail = inviteByEmail;
|
|
5
|
-
exports.mailInvitation = mailInvitation;
|
|
6
|
-
exports.acceptInvitation = acceptInvitation;
|
|
7
|
-
exports.cancelInvitation = cancelInvitation;
|
|
8
|
-
const typeorm_1 = require("typeorm");
|
|
9
|
-
const email_base_1 = require("@things-factory/email-base");
|
|
10
|
-
const env_1 = require("@things-factory/env");
|
|
11
|
-
const shell_1 = require("@things-factory/shell");
|
|
12
|
-
const invitation_js_1 = require("../service/invitation/invitation.js");
|
|
13
|
-
const user_js_1 = require("../service/user/user.js");
|
|
14
|
-
const invitation_email_js_1 = require("../templates/invitation-email.js");
|
|
15
|
-
const build_auth_url_js_1 = require("../utils/build-auth-url.js");
|
|
16
|
-
const invitation_state_js_1 = require("../utils/invitation-state.js");
|
|
17
|
-
const make_invitation_token_js_1 = require("./utils/make-invitation-token.js");
|
|
18
|
-
/**
|
|
19
|
-
* Inviting somebody into a domain, and their accepting it.
|
|
20
|
-
*
|
|
21
|
-
* ── What this is for, and what it is not ────────────────────────────────────
|
|
22
|
-
* `inviteUser` already puts a person who **has an account** into a domain, at once, with no mail
|
|
23
|
-
* and no say from them. That is the right tool when the administrator has the authority and the
|
|
24
|
-
* person already exists.
|
|
25
|
-
*
|
|
26
|
-
* This is for the case the `user-invite` screen records as having no path at all: **somebody with
|
|
27
|
-
* no account, in an installation with no default password.** Nobody can set their password for
|
|
28
|
-
* them, so the only way in is a mail they can prove they received. That is what the token is.
|
|
29
|
-
*
|
|
30
|
-
* ── Where the token sits ────────────────────────────────────────────────────
|
|
31
|
-
* In the link's **fragment** — `…/accept-invitation#<token>` — which browsers do not send to the
|
|
32
|
-
* server. So it stays out of access logs, proxy logs and `Referer`. The page strips it from the
|
|
33
|
-
* address bar as soon as it has read it.
|
|
34
|
-
*
|
|
35
|
-
* That leaves one way it can still travel: the mail itself being forwarded. Nothing about where
|
|
36
|
-
* the token sits can close that, so accepting asks for the invited address as well. Holding the
|
|
37
|
-
* link is then not enough on its own.
|
|
38
|
-
*
|
|
39
|
-
* ⚠ `/verify/:token`, `/reset-password?token=` and `/unlock-user?token=` still put theirs in the
|
|
40
|
-
* URL. A password reset token is not lighter than this one. Moving those three is its own job.
|
|
41
|
-
*/
|
|
42
|
-
/** Where the accept page answers. The mail, the page and the router all read this one spelling. */
|
|
43
|
-
exports.ACCEPT_INVITATION_PATH = '/auth/accept-invitation';
|
|
44
|
-
/** How long an invitation stands. Short, because it is a credential someone is holding. */
|
|
45
|
-
const EXPIRES_IN_HOURS = Number(env_1.config.get('invitation/expiresInHours', 48)) || 48;
|
|
46
|
-
function expiryFrom(now) {
|
|
47
|
-
return new Date(now.getTime() + EXPIRES_IN_HOURS * 60 * 60 * 1000);
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Create a pending invitation and mail it.
|
|
51
|
-
*
|
|
52
|
-
* ⚠ One pending invitation per person and domain. The history keeps every row, so this cannot be
|
|
53
|
-
* a unique index without forbidding "invited, left, invited again" — see the note on the entity.
|
|
54
|
-
* An existing pending one is re-issued rather than duplicated: a new token, a new expiry, and the
|
|
55
|
-
* same row, so the list shows one line per invitation rather than one per time somebody pressed
|
|
56
|
-
* the button.
|
|
57
|
-
*/
|
|
58
|
-
async function inviteByEmail({ email, domain, roles, user, context, tx }) {
|
|
59
|
-
const repository = (0, shell_1.getRepository)(invitation_js_1.Invitation, tx);
|
|
60
|
-
const now = new Date();
|
|
61
|
-
const alreadyIn = await (0, shell_1.getRepository)(user_js_1.User, tx).findOne({
|
|
62
|
-
where: { email: (0, typeorm_1.ILike)(email) },
|
|
63
|
-
relations: ['domains']
|
|
64
|
-
});
|
|
65
|
-
if (alreadyIn?.domains?.some(d => d.id === domain.id)) {
|
|
66
|
-
throw new shell_1.Refusal('invitation-already-a-member', { email, domain: domain.name }, `${email} is already in ${domain.name}.`);
|
|
67
|
-
}
|
|
68
|
-
const standing = await repository.findOne({
|
|
69
|
-
where: { email: (0, typeorm_1.ILike)(email), domain: { id: domain.id }, status: invitation_state_js_1.InvitationStatus.PENDING },
|
|
70
|
-
relations: ['domain']
|
|
71
|
-
});
|
|
72
|
-
const invitation = await repository.save({
|
|
73
|
-
...(standing || {}),
|
|
74
|
-
email,
|
|
75
|
-
domain,
|
|
76
|
-
roles: roles || [],
|
|
77
|
-
status: invitation_state_js_1.InvitationStatus.PENDING,
|
|
78
|
-
token: (0, make_invitation_token_js_1.makeInvitationToken)(),
|
|
79
|
-
expiresAt: expiryFrom(now),
|
|
80
|
-
acceptedAt: null,
|
|
81
|
-
creator: standing ? standing['creator'] : user,
|
|
82
|
-
updater: user
|
|
83
|
-
});
|
|
84
|
-
await mailInvitation(invitation, context);
|
|
85
|
-
return invitation;
|
|
86
|
-
}
|
|
87
|
-
/** Mail the link. Kept apart so re-sending does not have to go through creating one. */
|
|
88
|
-
async function mailInvitation(invitation, context) {
|
|
89
|
-
/*
|
|
90
|
-
* `buildAuthURL` reads the address this request arrived on, and falls back the way sign-in and
|
|
91
|
-
* OAuth do. The four mail controllers here each built theirs from `context.header.referer`
|
|
92
|
-
* instead, which **throws when the header is absent** — and the whole send sits in a
|
|
93
|
-
* `try { … } catch { return false }`, so it failed silently. This is that helper's first caller.
|
|
94
|
-
*/
|
|
95
|
-
/*
|
|
96
|
-
* ⚠ `/auth/…`. The route is declared as `/accept-invitation` on `authPublicProcessRouter`, and that
|
|
97
|
-
* router carries `prefix: '/auth'`. This line said `/accept-invitation` from 2026-09-20 until
|
|
98
|
-
* 09-24 — the link led to no route at all, fell through to the private history fallback, and the
|
|
99
|
-
* domain gate there turned the invitee away. It was the same defect as the `/auth/accept/<token>`
|
|
100
|
-
* link this replaced, made again. `tests/invitation-route.test.ts` now checks the path against
|
|
101
|
-
* the router's own table.
|
|
102
|
-
*/
|
|
103
|
-
const acceptUrl = `${(0, build_auth_url_js_1.buildAuthURL)(exports.ACCEPT_INVITATION_PATH, context)}#${invitation.token}`;
|
|
104
|
-
await (0, email_base_1.sendEmail)({
|
|
105
|
-
receiver: invitation.email,
|
|
106
|
-
subject: 'Invitation',
|
|
107
|
-
content: (0, invitation_email_js_1.getInvitationEmailForm)({
|
|
108
|
-
username: invitation.email,
|
|
109
|
-
email: invitation.email,
|
|
110
|
-
acceptUrl,
|
|
111
|
-
/* Printed as text too: the link's fragment needs JavaScript, and this does not. */
|
|
112
|
-
code: invitation.token
|
|
113
|
-
})
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Accept an invitation.
|
|
118
|
-
*
|
|
119
|
-
* The invited address has to be given as well as the token — see the note at the top. Everything
|
|
120
|
-
* that refuses here refuses the same way on purpose: a wrong token, a wrong address, an expired
|
|
121
|
-
* one and a cancelled one are one answer, so that trying them apart tells the caller nothing.
|
|
122
|
-
*/
|
|
123
|
-
async function acceptInvitation({ token, email, name, password, context, tx }) {
|
|
124
|
-
const repository = (0, shell_1.getRepository)(invitation_js_1.Invitation, tx);
|
|
125
|
-
const invitation = token && email
|
|
126
|
-
? await repository.findOne({ where: { token }, relations: ['domain', 'roles'] })
|
|
127
|
-
: null;
|
|
128
|
-
const addressMatches = invitation?.email && email && invitation.email.toLowerCase() === email.trim().toLowerCase();
|
|
129
|
-
if (!invitation || !addressMatches || !(0, invitation_state_js_1.mayAccept)(invitation)) {
|
|
130
|
-
throw new shell_1.Refusal('invitation-not-open', {}, 'This invitation cannot be used.');
|
|
131
|
-
}
|
|
132
|
-
const userRepository = (0, shell_1.getRepository)(user_js_1.User, tx);
|
|
133
|
-
let person = await userRepository.findOne({ where: { email: (0, typeorm_1.ILike)(invitation.email) }, relations: ['domains'] });
|
|
134
|
-
const created = !person;
|
|
135
|
-
if (!person) {
|
|
136
|
-
if (!password) {
|
|
137
|
-
throw new shell_1.Refusal('invitation-needs-a-password', {}, 'Choose a password to finish joining.');
|
|
138
|
-
}
|
|
139
|
-
/* The same rule the sign-up path applies; it throws its own message in the caller's language. */
|
|
140
|
-
user_js_1.User.validatePasswordByRule(password, context?.lng);
|
|
141
|
-
const salt = user_js_1.User.generateSalt();
|
|
142
|
-
person = await userRepository.save({
|
|
143
|
-
userType: 'user',
|
|
144
|
-
name: name || invitation.email,
|
|
145
|
-
username: invitation.email,
|
|
146
|
-
email: invitation.email,
|
|
147
|
-
salt,
|
|
148
|
-
password: user_js_1.User.encode(password, salt),
|
|
149
|
-
passwordUpdatedAt: new Date(),
|
|
150
|
-
/* Arriving through a mail they received is the verification, so they arrive usable. */
|
|
151
|
-
status: user_js_1.UserStatus.ACTIVATED,
|
|
152
|
-
domains: [invitation.domain]
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
else if (!person.domains?.some(d => d.id === invitation.domain.id)) {
|
|
156
|
-
person.domains = [...(person.domains || []), invitation.domain];
|
|
157
|
-
await userRepository.save(person);
|
|
158
|
-
}
|
|
159
|
-
/*
|
|
160
|
-
* Roles are given on arrival. Without them the person signs in and has no screens at all —
|
|
161
|
-
* which is why the invitation carries them, rather than leaving the administrator to notice
|
|
162
|
-
* afterwards.
|
|
163
|
-
*/
|
|
164
|
-
if (invitation.roles?.length) {
|
|
165
|
-
const loaded = await userRepository.findOne({ where: { id: person.id }, relations: ['roles'] });
|
|
166
|
-
const held = new Set((loaded?.roles || []).map(role => role.id));
|
|
167
|
-
await userRepository.save({
|
|
168
|
-
...loaded,
|
|
169
|
-
roles: [...(loaded?.roles || []), ...invitation.roles.filter(role => !held.has(role.id))]
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
await repository.save({
|
|
173
|
-
...invitation,
|
|
174
|
-
status: invitation_state_js_1.InvitationStatus.ACCEPTED,
|
|
175
|
-
acceptedAt: new Date()
|
|
176
|
-
});
|
|
177
|
-
return { domain: invitation.domain, created };
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Withdraw a pending invitation.
|
|
181
|
-
*
|
|
182
|
-
* The row stays and says it was cancelled. Deleting it would take with it the fact that somebody
|
|
183
|
-
* was invited at all, which is the thing the list exists to show.
|
|
184
|
-
*/
|
|
185
|
-
async function cancelInvitation(id, domain, user, tx) {
|
|
186
|
-
const repository = (0, shell_1.getRepository)(invitation_js_1.Invitation, tx);
|
|
187
|
-
const invitation = await repository.findOne({ where: { id, domain: { id: domain.id } }, relations: ['domain'] });
|
|
188
|
-
if (!invitation || !(0, invitation_state_js_1.mayAccept)(invitation)) {
|
|
189
|
-
/* Nothing to withdraw: already accepted, already cancelled, or already run out. */
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
await repository.save({ ...invitation, status: invitation_state_js_1.InvitationStatus.CANCELLED, updater: user });
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
//# sourceMappingURL=invitation.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invitation.js","sourceRoot":"","sources":["../../server/controllers/invitation.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, ILike } from 'typeorm'\n\nimport { sendEmail } from '@things-factory/email-base'\nimport { config } from '@things-factory/env'\nimport { Domain, getRepository, Refusal } from '@things-factory/shell'\n\nimport { Invitation } from '../service/invitation/invitation.js'\nimport { Role } from '../service/role/role.js'\nimport { User, UserStatus } from '../service/user/user.js'\nimport { getInvitationEmailForm } from '../templates/invitation-email.js'\nimport { buildAuthURL } from '../utils/build-auth-url.js'\nimport { InvitationStatus, mayAccept } from '../utils/invitation-state.js'\nimport { makeInvitationToken } from './utils/make-invitation-token.js'\n\n/**\n * Inviting somebody into a domain, and their accepting it.\n *\n * ── What this is for, and what it is not ────────────────────────────────────\n * `inviteUser` already puts a person who **has an account** into a domain, at once, with no mail\n * and no say from them. That is the right tool when the administrator has the authority and the\n * person already exists.\n *\n * This is for the case the `user-invite` screen records as having no path at all: **somebody with\n * no account, in an installation with no default password.** Nobody can set their password for\n * them, so the only way in is a mail they can prove they received. That is what the token is.\n *\n * ── Where the token sits ────────────────────────────────────────────────────\n * In the link's **fragment** — `…/accept-invitation#<token>` — which browsers do not send to the\n * server. So it stays out of access logs, proxy logs and `Referer`. The page strips it from the\n * address bar as soon as it has read it.\n *\n * That leaves one way it can still travel: the mail itself being forwarded. Nothing about where\n * the token sits can close that, so accepting asks for the invited address as well. Holding the\n * link is then not enough on its own.\n *\n * ⚠ `/verify/:token`, `/reset-password?token=` and `/unlock-user?token=` still put theirs in the\n * URL. A password reset token is not lighter than this one. Moving those three is its own job.\n */\n\n/** Where the accept page answers. The mail, the page and the router all read this one spelling. */\nexport const ACCEPT_INVITATION_PATH = '/auth/accept-invitation'\n\n/** How long an invitation stands. Short, because it is a credential someone is holding. */\nconst EXPIRES_IN_HOURS = Number(config.get('invitation/expiresInHours', 48)) || 48\n\nfunction expiryFrom(now: Date): Date {\n return new Date(now.getTime() + EXPIRES_IN_HOURS * 60 * 60 * 1000)\n}\n\n/**\n * Create a pending invitation and mail it.\n *\n * ⚠ One pending invitation per person and domain. The history keeps every row, so this cannot be\n * a unique index without forbidding \"invited, left, invited again\" — see the note on the entity.\n * An existing pending one is re-issued rather than duplicated: a new token, a new expiry, and the\n * same row, so the list shows one line per invitation rather than one per time somebody pressed\n * the button.\n */\nexport async function inviteByEmail({\n email,\n domain,\n roles,\n user,\n context,\n tx\n}: {\n email: string\n domain: Domain\n roles?: Role[]\n user?: User\n context: any\n tx?: EntityManager\n}): Promise<Invitation> {\n const repository = getRepository(Invitation, tx)\n const now = new Date()\n\n const alreadyIn = await getRepository(User, tx).findOne({\n where: { email: ILike(email) },\n relations: ['domains']\n })\n\n if (alreadyIn?.domains?.some(d => d.id === domain.id)) {\n throw new Refusal(\n 'invitation-already-a-member',\n { email, domain: domain.name },\n `${email} is already in ${domain.name}.`\n )\n }\n\n const standing = await repository.findOne({\n where: { email: ILike(email), domain: { id: domain.id }, status: InvitationStatus.PENDING },\n relations: ['domain']\n })\n\n const invitation = await repository.save({\n ...(standing || {}),\n email,\n domain,\n roles: roles || [],\n status: InvitationStatus.PENDING,\n token: makeInvitationToken(),\n expiresAt: expiryFrom(now),\n acceptedAt: null,\n creator: standing ? standing['creator'] : user,\n updater: user\n } as any)\n\n await mailInvitation(invitation, context)\n\n return invitation\n}\n\n/** Mail the link. Kept apart so re-sending does not have to go through creating one. */\nexport async function mailInvitation(invitation: Invitation, context: any): Promise<void> {\n /*\n * `buildAuthURL` reads the address this request arrived on, and falls back the way sign-in and\n * OAuth do. The four mail controllers here each built theirs from `context.header.referer`\n * instead, which **throws when the header is absent** — and the whole send sits in a\n * `try { … } catch { return false }`, so it failed silently. This is that helper's first caller.\n */\n /*\n * ⚠ `/auth/…`. The route is declared as `/accept-invitation` on `authPublicProcessRouter`, and that\n * router carries `prefix: '/auth'`. This line said `/accept-invitation` from 2026-09-20 until\n * 09-24 — the link led to no route at all, fell through to the private history fallback, and the\n * domain gate there turned the invitee away. It was the same defect as the `/auth/accept/<token>`\n * link this replaced, made again. `tests/invitation-route.test.ts` now checks the path against\n * the router's own table.\n */\n const acceptUrl = `${buildAuthURL(ACCEPT_INVITATION_PATH, context)}#${invitation.token}`\n\n await sendEmail({\n receiver: invitation.email,\n subject: 'Invitation',\n content: getInvitationEmailForm({\n username: invitation.email,\n email: invitation.email,\n acceptUrl,\n /* Printed as text too: the link's fragment needs JavaScript, and this does not. */\n code: invitation.token\n })\n })\n}\n\n/**\n * Accept an invitation.\n *\n * The invited address has to be given as well as the token — see the note at the top. Everything\n * that refuses here refuses the same way on purpose: a wrong token, a wrong address, an expired\n * one and a cancelled one are one answer, so that trying them apart tells the caller nothing.\n */\nexport async function acceptInvitation({\n token,\n email,\n name,\n password,\n context,\n tx\n}: {\n token: string\n email: string\n name?: string\n password?: string\n context: any\n tx?: EntityManager\n}): Promise<{ domain: Domain; created: boolean }> {\n const repository = getRepository(Invitation, tx)\n\n const invitation =\n token && email\n ? await repository.findOne({ where: { token }, relations: ['domain', 'roles'] })\n : null\n\n const addressMatches = invitation?.email && email && invitation.email.toLowerCase() === email.trim().toLowerCase()\n\n if (!invitation || !addressMatches || !mayAccept(invitation)) {\n throw new Refusal('invitation-not-open', {}, 'This invitation cannot be used.')\n }\n\n const userRepository = getRepository(User, tx)\n\n let person = await userRepository.findOne({ where: { email: ILike(invitation.email) }, relations: ['domains'] })\n const created = !person\n\n if (!person) {\n if (!password) {\n throw new Refusal('invitation-needs-a-password', {}, 'Choose a password to finish joining.')\n }\n\n /* The same rule the sign-up path applies; it throws its own message in the caller's language. */\n User.validatePasswordByRule(password, context?.lng)\n\n const salt = User.generateSalt()\n\n person = await userRepository.save({\n userType: 'user',\n name: name || invitation.email,\n username: invitation.email,\n email: invitation.email,\n salt,\n password: User.encode(password, salt),\n passwordUpdatedAt: new Date(),\n /* Arriving through a mail they received is the verification, so they arrive usable. */\n status: UserStatus.ACTIVATED,\n domains: [invitation.domain]\n } as any)\n } else if (!person.domains?.some(d => d.id === invitation.domain.id)) {\n person.domains = [...(person.domains || []), invitation.domain]\n await userRepository.save(person)\n }\n\n /*\n * Roles are given on arrival. Without them the person signs in and has no screens at all —\n * which is why the invitation carries them, rather than leaving the administrator to notice\n * afterwards.\n */\n if (invitation.roles?.length) {\n const loaded = await userRepository.findOne({ where: { id: person.id }, relations: ['roles'] })\n const held = new Set((loaded?.roles || []).map(role => role.id))\n\n await userRepository.save({\n ...loaded,\n roles: [...(loaded?.roles || []), ...invitation.roles.filter(role => !held.has(role.id))]\n } as any)\n }\n\n await repository.save({\n ...invitation,\n status: InvitationStatus.ACCEPTED,\n acceptedAt: new Date()\n } as any)\n\n return { domain: invitation.domain, created }\n}\n\n/**\n * Withdraw a pending invitation.\n *\n * The row stays and says it was cancelled. Deleting it would take with it the fact that somebody\n * was invited at all, which is the thing the list exists to show.\n */\nexport async function cancelInvitation(id: string, domain: Domain, user: User, tx?: EntityManager): Promise<boolean> {\n const repository = getRepository(Invitation, tx)\n\n const invitation = await repository.findOne({ where: { id, domain: { id: domain.id } }, relations: ['domain'] })\n\n if (!invitation || !mayAccept(invitation)) {\n /* Nothing to withdraw: already accepted, already cancelled, or already run out. */\n return false\n }\n\n await repository.save({ ...invitation, status: InvitationStatus.CANCELLED, updater: user } as any)\n\n return true\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function makeInvitationToken(): string;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.makeInvitationToken = makeInvitationToken;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const crypto_1 = tslib_1.__importDefault(require("crypto"));
|
|
6
|
-
function makeInvitationToken() {
|
|
7
|
-
return crypto_1.default.randomBytes(16).toString('hex');
|
|
8
|
-
}
|
|
9
|
-
//# sourceMappingURL=make-invitation-token.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"make-invitation-token.js","sourceRoot":"","sources":["../../../server/controllers/utils/make-invitation-token.ts"],"names":[],"mappings":";;AAEA,kDAEC;;AAJD,4DAA2B;AAE3B,SAAgB,mBAAmB;IACjC,OAAO,gBAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC/C,CAAC","sourcesContent":["import crypto from 'crypto'\n\nexport function makeInvitationToken() {\n return crypto.randomBytes(16).toString('hex')\n}\n"]}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.saveInvitationToken = saveInvitationToken;
|
|
4
|
-
const shell_1 = require("@things-factory/shell");
|
|
5
|
-
const invitation_js_1 = require("../../service/invitation/invitation.js");
|
|
6
|
-
async function saveInvitationToken(id, token) {
|
|
7
|
-
return await (0, shell_1.getRepository)(invitation_js_1.Invitation).save({
|
|
8
|
-
id,
|
|
9
|
-
token
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
//# sourceMappingURL=save-invitation-token.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"save-invitation-token.js","sourceRoot":"","sources":["../../../server/controllers/utils/save-invitation-token.ts"],"names":[],"mappings":";;AAIA,kDAKC;AATD,iDAAqD;AAErD,0EAAmE;AAE5D,KAAK,UAAU,mBAAmB,CAAC,EAAE,EAAE,KAAK;IACjD,OAAO,MAAM,IAAA,qBAAa,EAAC,0BAAU,CAAC,CAAC,IAAI,CAAC;QAC1C,EAAE;QACF,KAAK;KACN,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { getRepository } from '@things-factory/shell'\n\nimport { Invitation } from '../../service/invitation/invitation.js'\n\nexport async function saveInvitationToken(id, token) {\n return await getRepository(Invitation).save({\n id,\n token\n })\n}\n"]}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { Invitation } from './invitation.js';
|
|
2
|
-
import { InvitationQuery } from './invitation-query.js';
|
|
3
|
-
import { InvitationMutation } from './invitation-mutation.js';
|
|
4
|
-
export declare const entities: (typeof Invitation)[];
|
|
5
|
-
export declare const resolvers: (typeof InvitationQuery | typeof InvitationMutation)[];
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolvers = exports.entities = void 0;
|
|
4
|
-
const invitation_js_1 = require("./invitation.js");
|
|
5
|
-
const invitation_query_js_1 = require("./invitation-query.js");
|
|
6
|
-
const invitation_mutation_js_1 = require("./invitation-mutation.js");
|
|
7
|
-
exports.entities = [invitation_js_1.Invitation];
|
|
8
|
-
exports.resolvers = [invitation_query_js_1.InvitationQuery, invitation_mutation_js_1.InvitationMutation];
|
|
9
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/invitation/index.ts"],"names":[],"mappings":";;;AAAA,mDAA4C;AAC5C,+DAAuD;AACvD,qEAA6D;AAEhD,QAAA,QAAQ,GAAG,CAAC,0BAAU,CAAC,CAAA;AACvB,QAAA,SAAS,GAAG,CAAC,qCAAe,EAAE,2CAAkB,CAAC,CAAA","sourcesContent":["import { Invitation } from './invitation.js'\nimport { InvitationQuery } from './invitation-query.js'\nimport { InvitationMutation } from './invitation-mutation.js'\n\nexport const entities = [Invitation]\nexport const resolvers = [InvitationQuery, InvitationMutation]\n"]}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Invitation } from './invitation.js';
|
|
2
|
-
export declare class InvitationMutation {
|
|
3
|
-
/**
|
|
4
|
-
* Invite somebody into this domain by email.
|
|
5
|
-
*
|
|
6
|
-
* ⚠ The domain is the caller's own, never an argument. It used to be `reference`, a string the
|
|
7
|
-
* caller passed, which is how a door that reads by `{ email, reference, type }` ends up
|
|
8
|
-
* answering about other tenants. The domain comes from the request now.
|
|
9
|
-
*/
|
|
10
|
-
sendInvitation(email: string, context: ResolverContext, roleIds?: string[]): Promise<Invitation>;
|
|
11
|
-
/** Send the mail again for an invitation that is still open. A new token, a new expiry. */
|
|
12
|
-
resendInvitation(id: string, context: ResolverContext): Promise<boolean>;
|
|
13
|
-
/**
|
|
14
|
-
* Withdraw a pending invitation.
|
|
15
|
-
*
|
|
16
|
-
* The row stays and says it was cancelled. It used to be deleted by `{ email, reference, type }`
|
|
17
|
-
* — which erased the fact that anybody had been invited, and took an id from any tenant.
|
|
18
|
-
*/
|
|
19
|
-
cancelInvitation(id: string, context: ResolverContext): Promise<boolean>;
|
|
20
|
-
}
|