@things-factory/auth-base 10.1.246 → 10.1.250
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-server/controllers/account-mail.d.ts +29 -0
- package/dist-server/controllers/account-mail.js +28 -0
- package/dist-server/controllers/account-mail.js.map +1 -0
- package/dist-server/controllers/password-setup.js +2 -9
- package/dist-server/controllers/password-setup.js.map +1 -1
- package/dist-server/controllers/reset-password.js +2 -6
- package/dist-server/controllers/reset-password.js.map +1 -1
- package/dist-server/controllers/unlock-user.js +2 -7
- package/dist-server/controllers/unlock-user.js.map +1 -1
- package/dist-server/controllers/verification.js +2 -7
- package/dist-server/controllers/verification.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/tests/account-mail.test.ts +69 -0
- package/tests/mail-request-answers-db.test.ts +3 -0
- package/tests/password-setup-db.test.ts +2 -0
- package/tests/verification-token-db.test.ts +3 -0
- package/translations/en.json +13 -1
- package/translations/es.json +13 -1
- package/translations/ja.json +13 -1
- package/translations/ko.json +13 -1
- package/translations/ms.json +13 -1
- package/translations/zh.json +13 -1
- package/dist-server/templates/account-unlock-email.d.ts +0 -5
- package/dist-server/templates/account-unlock-email.js +0 -69
- package/dist-server/templates/account-unlock-email.js.map +0 -1
- package/dist-server/templates/reset-password-email.d.ts +0 -11
- package/dist-server/templates/reset-password-email.js +0 -73
- package/dist-server/templates/reset-password-email.js.map +0 -1
- package/dist-server/templates/verification-email.d.ts +0 -5
- package/dist-server/templates/verification-email.js +0 -70
- package/dist-server/templates/verification-email.js.map +0 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type MailWords } from '@things-factory/email-base';
|
|
2
|
+
/**
|
|
3
|
+
* The four mails an account gets, each with one link: confirm the address it signed up with, reset a forgotten
|
|
4
|
+
* password, set the password an administrator left waiting (ADR-0161), and unlock after too many wrong passwords.
|
|
5
|
+
* They are written in the person's language and sent in the same mail every module sends (email-base `mailForm`), so
|
|
6
|
+
* they carry the application's brand like any notice.
|
|
7
|
+
*/
|
|
8
|
+
export type AccountMailKind = 'verify-email' | 'reset-password' | 'set-password' | 'unlock-account';
|
|
9
|
+
export declare const ACCOUNT_MAIL_KINDS: readonly AccountMailKind[];
|
|
10
|
+
type Translate = (key: string, options?: Record<string, unknown>) => string;
|
|
11
|
+
/** What one account mail says, in the language `t` speaks. `brand` is the application's name. */
|
|
12
|
+
export declare function accountMailWords(t: Translate, kind: AccountMailKind, brand: string): MailWords;
|
|
13
|
+
/**
|
|
14
|
+
* The subject and HTML of an account mail to `user`. The language is the person's own, else the one their request was
|
|
15
|
+
* made in, else the deployment's default - the request is theirs for every mail but the setup link, which an
|
|
16
|
+
* administrator asks for.
|
|
17
|
+
*/
|
|
18
|
+
export declare function accountMail({ kind, user, link, context }: {
|
|
19
|
+
kind: AccountMailKind;
|
|
20
|
+
user: {
|
|
21
|
+
locale?: string | null;
|
|
22
|
+
};
|
|
23
|
+
link: string;
|
|
24
|
+
context: any;
|
|
25
|
+
}): {
|
|
26
|
+
subject: string;
|
|
27
|
+
content: string;
|
|
28
|
+
};
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ACCOUNT_MAIL_KINDS = void 0;
|
|
4
|
+
exports.accountMailWords = accountMailWords;
|
|
5
|
+
exports.accountMail = accountMail;
|
|
6
|
+
const email_base_1 = require("@things-factory/email-base");
|
|
7
|
+
const env_1 = require("@things-factory/env");
|
|
8
|
+
exports.ACCOUNT_MAIL_KINDS = ['verify-email', 'reset-password', 'set-password', 'unlock-account'];
|
|
9
|
+
/** What one account mail says, in the language `t` speaks. `brand` is the application's name. */
|
|
10
|
+
function accountMailWords(t, kind, brand) {
|
|
11
|
+
return {
|
|
12
|
+
subject: t(`title.account-mail-${kind}`),
|
|
13
|
+
body: t(`text.account-mail-${kind}`, { brand }),
|
|
14
|
+
action: t(`button.account-mail-${kind}`)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The subject and HTML of an account mail to `user`. The language is the person's own, else the one their request was
|
|
19
|
+
* made in, else the deployment's default - the request is theirs for every mail but the setup link, which an
|
|
20
|
+
* administrator asks for.
|
|
21
|
+
*/
|
|
22
|
+
function accountMail({ kind, user, link, context }) {
|
|
23
|
+
const lng = (0, email_base_1.languageOf)(user.locale, context?.lng || env_1.config.get('i18n/defaultLanguage', 'en'));
|
|
24
|
+
const brand = (0, email_base_1.appBrand)();
|
|
25
|
+
const words = accountMailWords(context.i18next.getFixedT(lng), kind, brand.name);
|
|
26
|
+
return { subject: words.subject, content: (0, email_base_1.mailForm)({ lang: lng, words, link, brand }) };
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=account-mail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-mail.js","sourceRoot":"","sources":["../../server/controllers/account-mail.ts"],"names":[],"mappings":";;;AAgBA,4CAMC;AAOD,kCAgBC;AA7CD,2DAA2F;AAC3F,6CAA4C;AAU/B,QAAA,kBAAkB,GAA+B,CAAC,cAAc,EAAE,gBAAgB,EAAE,cAAc,EAAE,gBAAgB,CAAC,CAAA;AAIlI,iGAAiG;AACjG,SAAgB,gBAAgB,CAAC,CAAY,EAAE,IAAqB,EAAE,KAAa;IACjF,OAAO;QACL,OAAO,EAAE,CAAC,CAAC,sBAAsB,IAAI,EAAE,CAAC;QACxC,IAAI,EAAE,CAAC,CAAC,qBAAqB,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;QAC/C,MAAM,EAAE,CAAC,CAAC,uBAAuB,IAAI,EAAE,CAAC;KACzC,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,OAAO,EAMR;IACC,MAAM,GAAG,GAAG,IAAA,uBAAU,EAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,YAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,IAAI,CAAC,CAAC,CAAA;IAC7F,MAAM,KAAK,GAAG,IAAA,qBAAQ,GAAE,CAAA;IACxB,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;IAEhF,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,IAAA,qBAAQ,EAAC,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAA;AACzF,CAAC","sourcesContent":["import { appBrand, languageOf, mailForm, type MailWords } from '@things-factory/email-base'\nimport { config } from '@things-factory/env'\n\n/**\n * The four mails an account gets, each with one link: confirm the address it signed up with, reset a forgotten\n * password, set the password an administrator left waiting (ADR-0161), and unlock after too many wrong passwords.\n * They are written in the person's language and sent in the same mail every module sends (email-base `mailForm`), so\n * they carry the application's brand like any notice.\n */\nexport type AccountMailKind = 'verify-email' | 'reset-password' | 'set-password' | 'unlock-account'\n\nexport const ACCOUNT_MAIL_KINDS: readonly AccountMailKind[] = ['verify-email', 'reset-password', 'set-password', 'unlock-account']\n\ntype Translate = (key: string, options?: Record<string, unknown>) => string\n\n/** What one account mail says, in the language `t` speaks. `brand` is the application's name. */\nexport function accountMailWords(t: Translate, kind: AccountMailKind, brand: string): MailWords {\n return {\n subject: t(`title.account-mail-${kind}`),\n body: t(`text.account-mail-${kind}`, { brand }),\n action: t(`button.account-mail-${kind}`)\n }\n}\n\n/**\n * The subject and HTML of an account mail to `user`. The language is the person's own, else the one their request was\n * made in, else the deployment's default - the request is theirs for every mail but the setup link, which an\n * administrator asks for.\n */\nexport function accountMail({\n kind,\n user,\n link,\n context\n}: {\n kind: AccountMailKind\n user: { locale?: string | null }\n link: string\n context: any\n}): { subject: string; content: string } {\n const lng = languageOf(user.locale, context?.lng || config.get('i18n/defaultLanguage', 'en'))\n const brand = appBrand()\n const words = accountMailWords(context.i18next.getFixedT(lng), kind, brand.name)\n\n return { subject: words.subject, content: mailForm({ lang: lng, words, link, brand }) }\n}\n"]}
|
|
@@ -6,9 +6,9 @@ const email_base_1 = require("@things-factory/email-base");
|
|
|
6
6
|
const shell_1 = require("@things-factory/shell");
|
|
7
7
|
const user_js_1 = require("../service/user/user.js");
|
|
8
8
|
const verification_token_js_1 = require("../service/verification-token/verification-token.js");
|
|
9
|
-
const reset_password_email_js_1 = require("../templates/reset-password-email.js");
|
|
10
9
|
const build_auth_url_js_1 = require("../utils/build-auth-url.js");
|
|
11
10
|
const verification_token_rule_js_1 = require("../utils/verification-token-rule.js");
|
|
11
|
+
const account_mail_js_1 = require("./account-mail.js");
|
|
12
12
|
const reset_password_js_1 = require("./reset-password.js");
|
|
13
13
|
const make_verification_token_js_1 = require("./utils/make-verification-token.js");
|
|
14
14
|
/**
|
|
@@ -60,14 +60,7 @@ async function issuePasswordSetupLink({ user, mail, context, tx }) {
|
|
|
60
60
|
*/
|
|
61
61
|
const sent = await (0, email_base_1.sendEmail)({
|
|
62
62
|
receiver: user.email,
|
|
63
|
-
|
|
64
|
-
content: (0, reset_password_email_js_1.getResetPasswordEmailForm)({
|
|
65
|
-
name: user.name,
|
|
66
|
-
resetUrl: link,
|
|
67
|
-
heading: 'Set your password',
|
|
68
|
-
action: 'set your password',
|
|
69
|
-
note: 'The link works once, for 48 hours. The page asks for this email address.'
|
|
70
|
-
})
|
|
63
|
+
...(0, account_mail_js_1.accountMail)({ kind: 'set-password', user, link, context })
|
|
71
64
|
});
|
|
72
65
|
if (!sent) {
|
|
73
66
|
throw new shell_1.Refusal('password-setup-mail-not-sent', { email: user.email }, `The mail to ${user.email} did not go out. Nothing was changed - copy the link instead.`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password-setup.js","sourceRoot":"","sources":["../../server/controllers/password-setup.ts"],"names":[],"mappings":";;AAsBA,oCAIC;AAcD,
|
|
1
|
+
{"version":3,"file":"password-setup.js","sourceRoot":"","sources":["../../server/controllers/password-setup.ts"],"names":[],"mappings":";;AAsBA,oCAIC;AAcD,wDAoDC;AA1FD,2DAAsD;AACtD,iDAA8D;AAE9D,qDAA0D;AAC1D,+FAA8G;AAC9G,kEAAyD;AACzD,oFAAuG;AACvG,uDAA+C;AAC/C,2DAAyD;AACzD,mFAA0E;AAE1E;;;;;;GAMG;AAEH,yFAAyF;AACzF,SAAgB,YAAY,CAAC,IAAmB;IAC9C,IAAI,CAAC,IAAI,GAAG,cAAI,CAAC,YAAY,EAAE,CAAA;IAC/B,IAAI,CAAC,QAAQ,GAAG,cAAI,CAAC,MAAM,CAAC,IAAA,kDAAqB,GAAE,GAAG,IAAA,kDAAqB,GAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;IACzF,IAAI,CAAC,MAAM,GAAG,oBAAU,CAAC,kBAAkB,CAAA;AAC7C,CAAC;AAED;;;;;;;;;;;GAWG;AACI,KAAK,UAAU,sBAAsB,CAAC,EAC3C,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,EAAE,EAMH;IACC,YAAY,CAAC,IAAI,CAAC,CAAA;IAClB,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;IAClB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;IACpB,IAAI,CAAC,gBAAgB,GAAG,CAAC,IAAI,CAAC,gBAAgB,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;IAExD,MAAM,IAAA,qBAAa,EAAC,cAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAExC,MAAM,KAAK,GAAG,IAAA,kDAAqB,GAAE,CAAA;IACrC,MAAM,SAAS,GAAG,IAAA,uDAA0B,EAAC,6CAAqB,CAAC,cAAc,CAAC,CAAA;IAElF,MAAM,IAAA,qBAAa,EAAC,yCAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC;QAC9C,MAAM,EAAE,IAAI,CAAC,EAAE;QACf,KAAK,EAAE,IAAA,kDAAqB,EAAC,KAAK,CAAC;QACnC,IAAI,EAAE,6CAAqB,CAAC,cAAc;QAC1C,SAAS;KACV,CAAC,CAAA;IAEF,MAAM,IAAI,GAAG,GAAG,IAAA,gCAAY,EAAC,uCAAmB,EAAE,OAAO,CAAC,IAAI,KAAK,EAAE,CAAA;IAErE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;IAC5C,CAAC;IAED;;;OAGG;IACH,MAAM,IAAI,GAAY,MAAM,IAAA,sBAAS,EAAC;QACpC,QAAQ,EAAE,IAAI,CAAC,KAAK;QACpB,GAAG,IAAA,6BAAW,EAAC,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;KAC9D,CAAC,CAAA;IAEF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,eAAO,CACf,8BAA8B,EAC9B,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EACrB,eAAe,IAAI,CAAC,KAAK,+DAA+D,CACzF,CAAA;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,CAAA;AACxD,CAAC","sourcesContent":["import { EntityManager } from 'typeorm'\n\nimport { sendEmail } from '@things-factory/email-base'\nimport { getRepository, Refusal } from '@things-factory/shell'\n\nimport { User, UserStatus } from '../service/user/user.js'\nimport { VerificationToken, VerificationTokenType } from '../service/verification-token/verification-token.js'\nimport { buildAuthURL } from '../utils/build-auth-url.js'\nimport { hashVerificationToken, verificationTokenExpiresAt } from '../utils/verification-token-rule.js'\nimport { accountMail } from './account-mail.js'\nimport { RESET_PASSWORD_PATH } from './reset-password.js'\nimport { makeVerificationToken } from './utils/make-verification-token.js'\n\n/**\n * An administrator never sets anybody's password (ADR-0161).\n *\n * An account an administrator makes, or one whose password they take back, is put **waiting for its password**: the\n * status `password_reset_required` and a hash of a random value nobody holds. The person sets their own through a\n * one-time link - mailed, or handed over by the administrator - on the same page a forgotten password is reset on.\n */\n\n/** A password nobody knows: what a waiting account carries until its person sets one. */\nexport function sealPassword(user: Partial<User>): void {\n user.salt = User.generateSalt()\n user.password = User.encode(makeVerificationToken() + makeVerificationToken(), user.salt)\n user.status = UserStatus.PWD_RESET_REQUIRED\n}\n\n/**\n * Put `user` waiting for its password and issue the link.\n *\n * Whatever password the account had stops working now, and its generation moves on, so a session opened before this\n * - by the person or by whoever made the administrator take the password back - ends with it.\n *\n * The person holds one live token at a time (the table is keyed by person), so issuing again retires the last link.\n *\n * @param mail true mails the link to the account's address; the answer then carries no link. A mail that does not go\n * out refuses, and the transaction takes back the rest.\n * @returns the link when it is not mailed - this is the only time it exists outside the person's hands.\n */\nexport async function issuePasswordSetupLink({\n user,\n mail,\n context,\n tx\n}: {\n user: User\n mail: boolean\n context: any\n tx?: EntityManager\n}): Promise<{ link: string | null; mailedTo: string | null; expiresAt: Date }> {\n sealPassword(user)\n user.failCount = 0\n user.lockedAt = null\n user.credentialSerial = (user.credentialSerial ?? 0) + 1\n\n await getRepository(User, tx).save(user)\n\n const token = makeVerificationToken()\n const expiresAt = verificationTokenExpiresAt(VerificationTokenType.PASSWORD_SETUP)\n\n await getRepository(VerificationToken, tx).save({\n userId: user.id,\n token: hashVerificationToken(token),\n type: VerificationTokenType.PASSWORD_SETUP,\n expiresAt\n })\n\n const link = `${buildAuthURL(RESET_PASSWORD_PATH, context)}#${token}`\n\n if (!mail) {\n return { link, mailedTo: null, expiresAt }\n }\n\n /*\n * `sendEmail` swallows a failure and answers nothing; a sent mail answers the transport's receipt. Its declared type\n * is `void`, so it is read as unknown here - the receipt is the only sign the mail went out.\n */\n const sent: unknown = await sendEmail({\n receiver: user.email,\n ...accountMail({ kind: 'set-password', user, link, context })\n })\n\n if (!sent) {\n throw new Refusal(\n 'password-setup-mail-not-sent',\n { email: user.email },\n `The mail to ${user.email} did not go out. Nothing was changed - copy the link instead.`\n )\n }\n\n return { link: null, mailedTo: user.email, expiresAt }\n}\n"]}
|
|
@@ -11,8 +11,8 @@ const auth_error_js_1 = require("../errors/auth-error.js");
|
|
|
11
11
|
const password_history_js_1 = require("../service/password-history/password-history.js");
|
|
12
12
|
const user_js_1 = require("../service/user/user.js");
|
|
13
13
|
const verification_token_js_1 = require("../service/verification-token/verification-token.js");
|
|
14
|
-
const reset_password_email_js_1 = require("../templates/reset-password-email.js");
|
|
15
14
|
const build_auth_url_js_1 = require("../utils/build-auth-url.js");
|
|
15
|
+
const account_mail_js_1 = require("./account-mail.js");
|
|
16
16
|
const find_verification_token_js_1 = require("./utils/find-verification-token.js");
|
|
17
17
|
const make_verification_token_js_1 = require("./utils/make-verification-token.js");
|
|
18
18
|
const save_verification_token_js_1 = require("./utils/save-verification-token.js");
|
|
@@ -31,11 +31,7 @@ async function sendPasswordResetEmail({ user, context }) {
|
|
|
31
31
|
var serviceUrl = `${(0, build_auth_url_js_1.buildAuthURL)(exports.RESET_PASSWORD_PATH, context)}#${token}`;
|
|
32
32
|
await (0, email_base_1.sendEmail)({
|
|
33
33
|
receiver: user.email,
|
|
34
|
-
|
|
35
|
-
content: (0, reset_password_email_js_1.getResetPasswordEmailForm)({
|
|
36
|
-
name: user.name,
|
|
37
|
-
resetUrl: serviceUrl
|
|
38
|
-
})
|
|
34
|
+
...(0, account_mail_js_1.accountMail)({ kind: 'reset-password', user, link: serviceUrl, context })
|
|
39
35
|
});
|
|
40
36
|
return true;
|
|
41
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reset-password.js","sourceRoot":"","sources":["../../server/controllers/reset-password.ts"],"names":[],"mappings":";;;AAwBA,
|
|
1
|
+
{"version":3,"file":"reset-password.js","sourceRoot":"","sources":["../../server/controllers/reset-password.ts"],"names":[],"mappings":"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","sourcesContent":["import { sendEmail } from '@things-factory/email-base'\nimport { config } from '@things-factory/env'\nimport { getRepository } from '@things-factory/shell'\n\nimport { PASSWORD_USED_PAST } from '../constants/error-code.js'\nimport { AuthError } from '../errors/auth-error.js'\nimport { PasswordHistory } from '../service/password-history/password-history.js'\nimport { User, UserStatus } from '../service/user/user.js'\nimport { VerificationTokenType } from '../service/verification-token/verification-token.js'\nimport { buildAuthURL } from '../utils/build-auth-url.js'\nimport { accountMail } from './account-mail.js'\nimport { consumeVerificationToken, findLiveVerificationToken } from './utils/find-verification-token.js'\nimport { makeVerificationToken } from './utils/make-verification-token.js'\nimport { saveVerificationToken } from './utils/save-verification-token.js'\n\nconst HISTORY_SIZE = config.get('password', { history: 0 }).history\n\n/**\n * The page the mail links to. The token rides after `#`, so the browser never sends it — not to our\n * access log, not to a proxy's. The page reads it and posts it. `tests/invitation-route.test.ts`\n * holds this against the router's table.\n */\nexport const RESET_PASSWORD_PATH = '/auth/reset-password'\n\nexport async function sendPasswordResetEmail({ user, context }) {\n try {\n var token = makeVerificationToken()\n var verifaction = await saveVerificationToken(user.id, token, VerificationTokenType.PASSWORD_RESET)\n\n if (verifaction) {\n var serviceUrl = `${buildAuthURL(RESET_PASSWORD_PATH, context)}#${token}`\n await sendEmail({\n receiver: user.email,\n ...accountMail({ kind: 'reset-password', user, link: serviceUrl, context })\n })\n\n return true\n }\n } catch (e) {\n return false\n }\n}\n\n/**\n * Set a new password through a link: the one a person mails themselves when they forgot theirs, or the setup link an\n * administrator issued (ADR-0161) - one page and one door for both.\n *\n * The address the link was for has to be given as well. A link is forwarded, left in a chat, copied onto paper; holding\n * it is not enough on its own. A wrong token, a wrong address and an expired link are one answer.\n */\nexport async function resetPassword(token, password, email, context) {\n const { t } = context\n\n const verificationToken =\n (await findLiveVerificationToken(token, VerificationTokenType.PASSWORD_RESET)) ||\n (await findLiveVerificationToken(token, VerificationTokenType.PASSWORD_SETUP))\n\n var user = verificationToken?.userId ? await getRepository(User).findOneBy({ id: verificationToken.userId }) : null\n\n const given = typeof email === 'string' ? email.trim().toLowerCase() : ''\n if (!user || !given || user.email?.toLowerCase() !== given) {\n throw new Error(t('text.invalid verification token'))\n }\n\n // if (user.status == UserStatus.INACTIVE) {\n // throw new Error(t('text.inactive user'))\n // }\n\n /* check if password is following the rule */\n User.validatePasswordByRule(password, context?.lng)\n\n user.password = User.encode(password, user.salt)\n\n if (HISTORY_SIZE > 0) {\n var passwordHistory: PasswordHistory = await getRepository(PasswordHistory).findOneBy({ userId: user.id })\n var history = []\n\n if (passwordHistory) {\n try {\n history = JSON.parse(passwordHistory.history)\n if (!(history instanceof Array)) {\n console.error('password history maybe currupted - not an array')\n history = []\n }\n } catch (e) {\n console.error('password history currupted - not json format')\n }\n\n const found = history.slice(0, HISTORY_SIZE).find(h => {\n return User.verify(h.password, password, h.salt)\n })\n\n if (found) {\n throw new AuthError({\n errorCode: PASSWORD_USED_PAST\n })\n }\n }\n }\n\n /*\n * A waiting account opens here (ADR-0161 decision 3). Any other status is left as it was - a locked account stays\n * locked, which is the unlock link's to undo (`tests/lock-recovery.test.ts`).\n */\n if (user.status === UserStatus.PWD_RESET_REQUIRED) {\n user.status = UserStatus.ACTIVATED\n user.failCount = 0\n }\n\n await getRepository(User).save({\n ...user,\n passwordUpdatedAt: new Date()\n })\n\n await consumeVerificationToken(verificationToken)\n\n if (HISTORY_SIZE > 0) {\n history = [\n {\n password: user.password,\n salt: user.salt\n },\n ...history\n ].slice(0, HISTORY_SIZE)\n\n await getRepository(PasswordHistory).save({\n userId: user.id,\n history: JSON.stringify(history)\n })\n }\n}\n"]}
|
|
@@ -7,8 +7,8 @@ const email_base_1 = require("@things-factory/email-base");
|
|
|
7
7
|
const shell_1 = require("@things-factory/shell");
|
|
8
8
|
const user_js_1 = require("../service/user/user.js");
|
|
9
9
|
const verification_token_js_1 = require("../service/verification-token/verification-token.js");
|
|
10
|
-
const account_unlock_email_js_1 = require("../templates/account-unlock-email.js");
|
|
11
10
|
const build_auth_url_js_1 = require("../utils/build-auth-url.js");
|
|
11
|
+
const account_mail_js_1 = require("./account-mail.js");
|
|
12
12
|
const find_verification_token_js_1 = require("./utils/find-verification-token.js");
|
|
13
13
|
const make_verification_token_js_1 = require("./utils/make-verification-token.js");
|
|
14
14
|
const save_verification_token_js_1 = require("./utils/save-verification-token.js");
|
|
@@ -26,12 +26,7 @@ async function sendUnlockUserEmail({ user, context }) {
|
|
|
26
26
|
var serviceUrl = `${(0, build_auth_url_js_1.buildAuthURL)(exports.UNLOCK_USER_PATH, context)}#${token}`;
|
|
27
27
|
await (0, email_base_1.sendEmail)({
|
|
28
28
|
receiver: user.email,
|
|
29
|
-
|
|
30
|
-
content: (0, account_unlock_email_js_1.getUnlockUserEmailForm)({
|
|
31
|
-
username: user.username,
|
|
32
|
-
name: user.name,
|
|
33
|
-
resetUrl: serviceUrl
|
|
34
|
-
})
|
|
29
|
+
...(0, account_mail_js_1.accountMail)({ kind: 'unlock-account', user, link: serviceUrl, context })
|
|
35
30
|
});
|
|
36
31
|
return true;
|
|
37
32
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unlock-user.js","sourceRoot":"","sources":["../../server/controllers/unlock-user.ts"],"names":[],"mappings":";;;AAkBA,
|
|
1
|
+
{"version":3,"file":"unlock-user.js","sourceRoot":"","sources":["../../server/controllers/unlock-user.ts"],"names":[],"mappings":";;;AAkBA,kDAiBC;AAED,gCAuCC;AA5ED,2DAAsD;AACtD,iDAAqD;AAErD,qDAA0D;AAC1D,+FAA2F;AAC3F,kEAAyD;AACzD,uDAA+C;AAC/C,mFAAwG;AACxG,mFAA0E;AAC1E,mFAA0E;AAE1E;;;;GAIG;AACU,QAAA,gBAAgB,GAAG,mBAAmB,CAAA;AAE5C,KAAK,UAAU,mBAAmB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;IACzD,IAAI,CAAC;QACH,IAAI,KAAK,GAAG,IAAA,kDAAqB,GAAE,CAAA;QACnC,IAAI,WAAW,GAAG,MAAM,IAAA,kDAAqB,EAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,6CAAqB,CAAC,MAAM,CAAC,CAAA;QAE3F,IAAI,WAAW,EAAE,CAAC;YAChB,IAAI,UAAU,GAAG,GAAG,IAAA,gCAAY,EAAC,wBAAgB,EAAE,OAAO,CAAC,IAAI,KAAK,EAAE,CAAA;YACtE,MAAM,IAAA,sBAAS,EAAC;gBACd,QAAQ,EAAE,IAAI,CAAC,KAAK;gBACpB,GAAG,IAAA,6BAAW,EAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;aAC5E,CAAC,CAAA;YAEF,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,UAAU,CAAC,KAAK,EAAE,QAAQ;IAC9C;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,IAAA,sDAAyB,EAAC,KAAK,EAAE,6CAAqB,CAAC,MAAM,CAAC,CAAA;IAEzF;iFAC6E;IAC7E,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,GAAG,YAAY,CAAA;IAE/B,IAAI,QAAQ,GAAG,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;IAClE,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC3B,IAAI,QAAQ,CAAC,MAAM,IAAI,oBAAU,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAEtD,QAAQ,CAAC,MAAM,GAAG,oBAAU,CAAC,SAAS,CAAA;IACtC,QAAQ,CAAC,QAAQ,GAAG,cAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;IACxD,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAA;IAEtB,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACxC,MAAM,IAAA,qDAAwB,EAAC,YAAY,CAAC,CAAA;IAE5C,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { sendEmail } from '@things-factory/email-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { User, UserStatus } from '../service/user/user.js'\nimport { VerificationTokenType } from '../service/verification-token/verification-token.js'\nimport { buildAuthURL } from '../utils/build-auth-url.js'\nimport { accountMail } from './account-mail.js'\nimport { consumeVerificationToken, findLiveVerificationToken } from './utils/find-verification-token.js'\nimport { makeVerificationToken } from './utils/make-verification-token.js'\nimport { saveVerificationToken } from './utils/save-verification-token.js'\n\n/**\n * The page the mail links to. The token rides after `#`, so the browser never sends it — not to our\n * access log, not to a proxy's. The page reads it and posts it. `tests/invitation-route.test.ts`\n * holds this against the router's table.\n */\nexport const UNLOCK_USER_PATH = '/auth/unlock-user'\n\nexport async function sendUnlockUserEmail({ user, context }) {\n try {\n var token = makeVerificationToken()\n var verifaction = await saveVerificationToken(user.id, token, VerificationTokenType.UNLOCK)\n\n if (verifaction) {\n var serviceUrl = `${buildAuthURL(UNLOCK_USER_PATH, context)}#${token}`\n await sendEmail({\n receiver: user.email,\n ...accountMail({ kind: 'unlock-account', user, link: serviceUrl, context })\n })\n\n return true\n }\n } catch (e) {\n return false\n }\n}\n\nexport async function unlockUser(token, password) {\n /*\n * An absent condition is not a narrow condition — TypeORM drops `undefined` from `where`.\n *\n * Without this guard, a call with no token searched `{ type: UNLOCK }` alone and matched\n * **anyone's** pending unlock token, then handed that account the password the caller chose.\n * Both halves are reachable without credentials: five wrong guesses lock a victim and mint the\n * token, and `/auth/unlock-user` accepted a body with a password and no token because its own\n * guard read `!(token || password)`.\n *\n * [확인함] 2026-09-09 · locked a victim by failing signin five times, posted a password with no\n * token, and signed in as them — status activated, failCount 0, password the attacker's.\n */\n if (!token || !password) {\n return false\n }\n\n const verification = await findLiveVerificationToken(token, VerificationTokenType.UNLOCK)\n\n /* Refuse a token that is gone, rather than destructuring null — a reused link showed the\n screen a 500 instead of a reason. `resetPassword` refuses the same way. */\n if (!verification?.userId) {\n return false\n }\n\n const { userId } = verification\n\n var userInfo = await getRepository(User).findOneBy({ id: userId })\n if (!userInfo) return false\n if (userInfo.status != UserStatus.LOCKED) return false\n\n userInfo.status = UserStatus.ACTIVATED\n userInfo.password = User.encode(password, userInfo.salt)\n userInfo.failCount = 0\n\n await getRepository(User).save(userInfo)\n await consumeVerificationToken(verification)\n\n return true\n}\n"]}
|
|
@@ -9,8 +9,8 @@ const shell_1 = require("@things-factory/shell");
|
|
|
9
9
|
const auth_error_js_1 = require("../errors/auth-error.js");
|
|
10
10
|
const user_js_1 = require("../service/user/user.js");
|
|
11
11
|
const verification_token_js_1 = require("../service/verification-token/verification-token.js");
|
|
12
|
-
const verification_email_js_1 = require("../templates/verification-email.js");
|
|
13
12
|
const build_auth_url_js_1 = require("../utils/build-auth-url.js");
|
|
13
|
+
const account_mail_js_1 = require("./account-mail.js");
|
|
14
14
|
const find_verification_token_js_1 = require("./utils/find-verification-token.js");
|
|
15
15
|
const make_verification_token_js_1 = require("./utils/make-verification-token.js");
|
|
16
16
|
const save_verification_token_js_1 = require("./utils/save-verification-token.js");
|
|
@@ -28,12 +28,7 @@ async function sendVerificationEmail({ user, context }) {
|
|
|
28
28
|
var serviceUrl = `${(0, build_auth_url_js_1.buildAuthURL)(exports.VERIFY_EMAIL_PATH, context)}#${token}`;
|
|
29
29
|
await (0, email_base_1.sendEmail)({
|
|
30
30
|
receiver: user.email,
|
|
31
|
-
|
|
32
|
-
content: (0, verification_email_js_1.getVerificationEmailForm)({
|
|
33
|
-
username: user.username,
|
|
34
|
-
name: user.name,
|
|
35
|
-
verifyUrl: serviceUrl
|
|
36
|
-
})
|
|
31
|
+
...(0, account_mail_js_1.accountMail)({ kind: 'verify-email', user, link: serviceUrl, context })
|
|
37
32
|
});
|
|
38
33
|
return true;
|
|
39
34
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verification.js","sourceRoot":"","sources":["../../server/controllers/verification.ts"],"names":[],"mappings":";;;AAmBA,
|
|
1
|
+
{"version":3,"file":"verification.js","sourceRoot":"","sources":["../../server/controllers/verification.ts"],"names":[],"mappings":"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","sourcesContent":["import { sendEmail } from '@things-factory/email-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { AuthError } from '../errors/auth-error.js'\nimport { User, UserStatus } from '../service/user/user.js'\nimport { VerificationTokenType } from '../service/verification-token/verification-token.js'\nimport { buildAuthURL } from '../utils/build-auth-url.js'\nimport { accountMail } from './account-mail.js'\nimport { consumeVerificationToken, findLiveVerificationToken } from './utils/find-verification-token.js'\nimport { makeVerificationToken } from './utils/make-verification-token.js'\nimport { saveVerificationToken } from './utils/save-verification-token.js'\n\n/**\n * The page the mail links to. The token rides after `#`, so the browser never sends it — not to our\n * access log, not to a proxy's. The page reads it and posts it. `tests/invitation-route.test.ts`\n * holds this against the router's table.\n */\nexport const VERIFY_EMAIL_PATH = '/auth/verify'\n\nexport async function sendVerificationEmail({ user, context }) {\n try {\n var token = makeVerificationToken()\n var verifaction = await saveVerificationToken(user.id, token)\n\n if (verifaction) {\n var serviceUrl = `${buildAuthURL(VERIFY_EMAIL_PATH, context)}#${token}`\n await sendEmail({\n receiver: user.email,\n ...accountMail({ kind: 'verify-email', user, link: serviceUrl, context })\n })\n\n return true\n }\n } catch (e) {\n return false\n }\n}\n\nexport async function verify(token) {\n /* Activation only. This read the token alone, so a reset or unlock link confirmed an address too. */\n var verification = await findLiveVerificationToken(token, VerificationTokenType.ACTIVATION)\n\n if (!verification) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.VERIFICATION_ERROR\n })\n }\n\n var userInfo = await getRepository(User).findOneBy({ id: verification.userId })\n if (!userInfo) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.VERIFICATION_ERROR\n })\n }\n\n if (!(userInfo.status == UserStatus.INACTIVE || userInfo.status == UserStatus.LOCKED)) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.VERIFICATION_ERROR\n })\n }\n\n userInfo.status = UserStatus.ACTIVATED\n userInfo.failCount = 0\n\n await getRepository(User).save(userInfo)\n await consumeVerificationToken(verification)\n}\n\n/**\n * `'not-due'` when no account at this address waits for confirmation, so the route can answer it\n * the way it answers a sent mail. `'failed'` only when a mail was due and did not go.\n */\nexport async function resendVerificationEmail(email, context): Promise<'sent' | 'not-due' | 'failed'> {\n /* TypeORM drops an undefined condition from `where` - without this, a post with no address\n matched the first account in the table and mailed it. */\n if (!email) return 'not-due'\n\n var user = await getRepository(User).findOne({\n where: {\n email\n }\n })\n\n if (!user) return 'not-due'\n if (user.status == UserStatus.ACTIVATED) return 'not-due'\n\n return (await sendVerificationEmail({ user, context })) ? 'sent' : 'failed'\n}\n"]}
|