@things-factory/auth-base 10.1.215 → 10.1.223
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/role/role-mutation.js +5 -0
- package/dist-server/service/role/role-mutation.js.map +1 -1
- package/dist-server/service/role/role-types.d.ts +1 -0
- package/dist-server/service/role/role-types.js +4 -0
- package/dist-server/service/role/role-types.js.map +1 -1
- package/dist-server/service/role/role.d.ts +10 -0
- package/dist-server/service/role/role.js +5 -0
- package/dist-server/service/role/role.js.map +1 -1
- package/dist-server/service/role-template/role-template-gap-resolver.d.ts +2 -2
- package/dist-server/service/role-template/role-template-gap-resolver.js +7 -13
- package/dist-server/service/role-template/role-template-gap-resolver.js.map +1 -1
- package/dist-server/service/role-template/role-template-mutation.d.ts +7 -4
- package/dist-server/service/role-template/role-template-mutation.js +15 -15
- package/dist-server/service/role-template/role-template-mutation.js.map +1 -1
- package/dist-server/service/role-template/role-template-types.d.ts +3 -3
- package/dist-server/service/role-template/role-template-types.js.map +1 -1
- package/dist-server/service/role-template/role-template.d.ts +14 -15
- package/dist-server/service/role-template/role-template.js +8 -8
- package/dist-server/service/role-template/role-template.js.map +1 -1
- package/dist-server/service/user/user-mutation.d.ts +27 -6
- package/dist-server/service/user/user-mutation.js +26 -114
- package/dist-server/service/user/user-mutation.js.map +1 -1
- package/dist-server/service/user/user-query.d.ts +3 -4
- package/dist-server/service/user/user-query.js +7 -32
- 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/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 +17 -16
- package/tests/password-setup-db.test.ts +213 -0
- package/tests/picture-reducer.test.ts +25 -0
- package/tests/role-template-gap-db.test.ts +58 -7
- package/tests/role-template-seed-db.test.ts +5 -4
- package/tests/verification-token-db.test.ts +12 -7
- package/translations/en.json +2 -2
- package/translations/es.json +2 -2
- package/translations/ja.json +2 -2
- package/translations/ko.json +2 -2
- package/translations/ms.json +2 -2
- package/translations/zh.json +2 -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
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sealPassword = sealPassword;
|
|
4
|
+
exports.issuePasswordSetupLink = issuePasswordSetupLink;
|
|
5
|
+
const email_base_1 = require("@things-factory/email-base");
|
|
6
|
+
const shell_1 = require("@things-factory/shell");
|
|
7
|
+
const user_js_1 = require("../service/user/user.js");
|
|
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
|
+
const build_auth_url_js_1 = require("../utils/build-auth-url.js");
|
|
11
|
+
const verification_token_rule_js_1 = require("../utils/verification-token-rule.js");
|
|
12
|
+
const reset_password_js_1 = require("./reset-password.js");
|
|
13
|
+
const make_verification_token_js_1 = require("./utils/make-verification-token.js");
|
|
14
|
+
/**
|
|
15
|
+
* An administrator never sets anybody's password (ADR-0161).
|
|
16
|
+
*
|
|
17
|
+
* An account an administrator makes, or one whose password they take back, is put **waiting for its password**: the
|
|
18
|
+
* status `password_reset_required` and a hash of a random value nobody holds. The person sets their own through a
|
|
19
|
+
* one-time link - mailed, or handed over by the administrator - on the same page a forgotten password is reset on.
|
|
20
|
+
*/
|
|
21
|
+
/** A password nobody knows: what a waiting account carries until its person sets one. */
|
|
22
|
+
function sealPassword(user) {
|
|
23
|
+
user.salt = user_js_1.User.generateSalt();
|
|
24
|
+
user.password = user_js_1.User.encode((0, make_verification_token_js_1.makeVerificationToken)() + (0, make_verification_token_js_1.makeVerificationToken)(), user.salt);
|
|
25
|
+
user.status = user_js_1.UserStatus.PWD_RESET_REQUIRED;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Put `user` waiting for its password and issue the link.
|
|
29
|
+
*
|
|
30
|
+
* Whatever password the account had stops working now, and its generation moves on, so a session opened before this
|
|
31
|
+
* - by the person or by whoever made the administrator take the password back - ends with it.
|
|
32
|
+
*
|
|
33
|
+
* The person holds one live token at a time (the table is keyed by person), so issuing again retires the last link.
|
|
34
|
+
*
|
|
35
|
+
* @param mail true mails the link to the account's address; the answer then carries no link. A mail that does not go
|
|
36
|
+
* out refuses, and the transaction takes back the rest.
|
|
37
|
+
* @returns the link when it is not mailed - this is the only time it exists outside the person's hands.
|
|
38
|
+
*/
|
|
39
|
+
async function issuePasswordSetupLink({ user, mail, context, tx }) {
|
|
40
|
+
sealPassword(user);
|
|
41
|
+
user.failCount = 0;
|
|
42
|
+
user.lockedAt = null;
|
|
43
|
+
user.credentialSerial = (user.credentialSerial ?? 0) + 1;
|
|
44
|
+
await (0, shell_1.getRepository)(user_js_1.User, tx).save(user);
|
|
45
|
+
const token = (0, make_verification_token_js_1.makeVerificationToken)();
|
|
46
|
+
const expiresAt = (0, verification_token_rule_js_1.verificationTokenExpiresAt)(verification_token_js_1.VerificationTokenType.PASSWORD_SETUP);
|
|
47
|
+
await (0, shell_1.getRepository)(verification_token_js_1.VerificationToken, tx).save({
|
|
48
|
+
userId: user.id,
|
|
49
|
+
token: (0, verification_token_rule_js_1.hashVerificationToken)(token),
|
|
50
|
+
type: verification_token_js_1.VerificationTokenType.PASSWORD_SETUP,
|
|
51
|
+
expiresAt
|
|
52
|
+
});
|
|
53
|
+
const link = `${(0, build_auth_url_js_1.buildAuthURL)(reset_password_js_1.RESET_PASSWORD_PATH, context)}#${token}`;
|
|
54
|
+
if (!mail) {
|
|
55
|
+
return { link, mailedTo: null, expiresAt };
|
|
56
|
+
}
|
|
57
|
+
/*
|
|
58
|
+
* `sendEmail` swallows a failure and answers nothing; a sent mail answers the transport's receipt. Its declared type
|
|
59
|
+
* is `void`, so it is read as unknown here - the receipt is the only sign the mail went out.
|
|
60
|
+
*/
|
|
61
|
+
const sent = await (0, email_base_1.sendEmail)({
|
|
62
|
+
receiver: user.email,
|
|
63
|
+
subject: 'Set your password',
|
|
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
|
+
})
|
|
71
|
+
});
|
|
72
|
+
if (!sent) {
|
|
73
|
+
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.`);
|
|
74
|
+
}
|
|
75
|
+
return { link: null, mailedTo: user.email, expiresAt };
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=password-setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-setup.js","sourceRoot":"","sources":["../../server/controllers/password-setup.ts"],"names":[],"mappings":";;AAsBA,oCAIC;AAcD,wDA2DC;AAjGD,2DAAsD;AACtD,iDAA8D;AAE9D,qDAA0D;AAC1D,+FAA8G;AAC9G,kFAAgF;AAChF,kEAAyD;AACzD,oFAAuG;AACvG,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,OAAO,EAAE,mBAAmB;QAC5B,OAAO,EAAE,IAAA,mDAAyB,EAAC;YACjC,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,QAAQ,EAAE,IAAI;YACd,OAAO,EAAE,mBAAmB;YAC5B,MAAM,EAAE,mBAAmB;YAC3B,IAAI,EAAE,0EAA0E;SACjF,CAAC;KACH,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 { getResetPasswordEmailForm } from '../templates/reset-password-email.js'\nimport { buildAuthURL } from '../utils/build-auth-url.js'\nimport { hashVerificationToken, verificationTokenExpiresAt } from '../utils/verification-token-rule.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 subject: 'Set your password',\n content: getResetPasswordEmailForm({\n name: user.name,\n resetUrl: link,\n heading: 'Set your password',\n action: 'set your password',\n note: 'The link works once, for 48 hours. The page asks for this email address.'\n })\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"]}
|
|
@@ -8,4 +8,11 @@ export declare function sendPasswordResetEmail({ user, context }: {
|
|
|
8
8
|
user: any;
|
|
9
9
|
context: any;
|
|
10
10
|
}): Promise<boolean>;
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Set a new password through a link: the one a person mails themselves when they forgot theirs, or the setup link an
|
|
13
|
+
* administrator issued (ADR-0161) - one page and one door for both.
|
|
14
|
+
*
|
|
15
|
+
* The address the link was for has to be given as well. A link is forwarded, left in a chat, copied onto paper; holding
|
|
16
|
+
* it is not enough on its own. A wrong token, a wrong address and an expired link are one answer.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resetPassword(token: any, password: any, email: any, context: any): Promise<void>;
|
|
@@ -44,20 +44,22 @@ async function sendPasswordResetEmail({ user, context }) {
|
|
|
44
44
|
return false;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Set a new password through a link: the one a person mails themselves when they forgot theirs, or the setup link an
|
|
49
|
+
* administrator issued (ADR-0161) - one page and one door for both.
|
|
50
|
+
*
|
|
51
|
+
* The address the link was for has to be given as well. A link is forwarded, left in a chat, copied onto paper; holding
|
|
52
|
+
* it is not enough on its own. A wrong token, a wrong address and an expired link are one answer.
|
|
53
|
+
*/
|
|
54
|
+
async function resetPassword(token, password, email, context) {
|
|
48
55
|
const { t } = context;
|
|
49
|
-
const verificationToken = await (0, find_verification_token_js_1.findLiveVerificationToken)(token, verification_token_js_1.VerificationTokenType.PASSWORD_RESET)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (!userId) {
|
|
56
|
+
const verificationToken = (await (0, find_verification_token_js_1.findLiveVerificationToken)(token, verification_token_js_1.VerificationTokenType.PASSWORD_RESET)) ||
|
|
57
|
+
(await (0, find_verification_token_js_1.findLiveVerificationToken)(token, verification_token_js_1.VerificationTokenType.PASSWORD_SETUP));
|
|
58
|
+
var user = verificationToken?.userId ? await (0, shell_1.getRepository)(user_js_1.User).findOneBy({ id: verificationToken.userId }) : null;
|
|
59
|
+
const given = typeof email === 'string' ? email.trim().toLowerCase() : '';
|
|
60
|
+
if (!user || !given || user.email?.toLowerCase() !== given) {
|
|
55
61
|
throw new Error(t('text.invalid verification token'));
|
|
56
62
|
}
|
|
57
|
-
var user = await (0, shell_1.getRepository)(user_js_1.User).findOneBy({ id: userId });
|
|
58
|
-
if (!user) {
|
|
59
|
-
throw new Error(t('error.user not found'));
|
|
60
|
-
}
|
|
61
63
|
// if (user.status == UserStatus.INACTIVE) {
|
|
62
64
|
// throw new Error(t('text.inactive user'))
|
|
63
65
|
// }
|
|
@@ -88,6 +90,14 @@ async function resetPassword(token, password, context) {
|
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
}
|
|
93
|
+
/*
|
|
94
|
+
* A waiting account opens here (ADR-0161 decision 3). Any other status is left as it was - a locked account stays
|
|
95
|
+
* locked, which is the unlock link's to undo (`tests/lock-recovery.test.ts`).
|
|
96
|
+
*/
|
|
97
|
+
if (user.status === user_js_1.UserStatus.PWD_RESET_REQUIRED) {
|
|
98
|
+
user.status = user_js_1.UserStatus.ACTIVATED;
|
|
99
|
+
user.failCount = 0;
|
|
100
|
+
}
|
|
91
101
|
await (0, shell_1.getRepository)(user_js_1.User).save({
|
|
92
102
|
...user,
|
|
93
103
|
passwordUpdatedAt: new Date()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reset-password.js","sourceRoot":"","sources":["../../server/controllers/reset-password.ts"],"names":[],"mappings":";;;AAwBA,wDAqBC;
|
|
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 { getResetPasswordEmailForm } from '../templates/reset-password-email.js'\nimport { buildAuthURL } from '../utils/build-auth-url.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 subject: 'Reset your password',\n content: getResetPasswordEmailForm({\n name: user.name,\n resetUrl: serviceUrl\n })\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"]}
|
|
@@ -144,6 +144,16 @@ async function admitSignin(user, context, verifySecret) {
|
|
|
144
144
|
user.lockedAt = null;
|
|
145
145
|
await repository.save(user);
|
|
146
146
|
}
|
|
147
|
+
/*
|
|
148
|
+
* Waiting for the password its person sets through a setup link (ADR-0161). Refused before the secret is looked at:
|
|
149
|
+
* the password it carries is one nobody holds, so a guess against it is not a failure to count - and a passkey does
|
|
150
|
+
* not open it either.
|
|
151
|
+
*/
|
|
152
|
+
if (user.status == user_js_1.UserStatus.PWD_RESET_REQUIRED) {
|
|
153
|
+
throw new auth_error_js_1.AuthError({
|
|
154
|
+
errorCode: auth_error_js_1.AuthError.ERROR_CODES.PASSWORD_SETUP_PENDING
|
|
155
|
+
});
|
|
156
|
+
}
|
|
147
157
|
await verifySecret();
|
|
148
158
|
if (user.status == user_js_1.UserStatus.INACTIVE) {
|
|
149
159
|
throw new auth_error_js_1.AuthError({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signin.js","sourceRoot":"","sources":["../../server/controllers/signin.ts"],"names":[],"mappings":"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","sourcesContent":["import { ILike } from 'typeorm'\nimport { getRepository } from '@things-factory/shell'\n\nimport { sendUnlockUserEmail } from '../controllers/unlock-user.js'\nimport { AuthError } from '../errors/auth-error.js'\nimport { User, UserStatus } from '../service/user/user.js'\nimport { isAccountLocked } from '../utils/account-lock-rule.js'\nimport { lockDurationMinutes } from '../utils/lock-duration.js'\nimport { recordAuditAction } from '../service/audit/audit-recorder.js'\nimport { errorCodeOf } from '../service/audit/audit-target.js'\n\nexport type SigninMethod = 'password' | 'passkey'\n\nexport type SigninAttempt = { userId?: string }\n\n/**\n * A sign-in, and its audit (ADR-0141 §3.3): a success or a failure with its code. A failure is\n * written on its own and stays when the request fails (§4-3). An account that does not exist leaves\n * no actor - the name typed in is not a person.\n */\nexport async function signin(attrs: { username: string; password: string }, context?) {\n return await auditedSignin('password', context, attempt => signinOnce(attrs, context, attempt))\n}\n\n/**\n * The audit around one way of signing in. `run` puts the person in `attempt` as soon as it knows\n * who it is, so a failure after that names them. `detail.method` tells a password from a passkey\n * (ADR-0156 decision 4).\n */\nexport async function auditedSignin<T extends { user: User }>(\n method: SigninMethod,\n context,\n run: (attempt: SigninAttempt) => Promise<T>\n): Promise<T> {\n const attempt: SigninAttempt = {}\n const domainId = context?.state?.domain?.id ?? null\n const detail = { method }\n\n /*\n * Not awaited: the answer does not wait on its record, and a write that fails is counted, never\n * thrown (`recordAuditAction` without a transaction).\n */\n try {\n const answer = await run(attempt)\n void recordAuditAction({ kind: 'auth.signin', outcome: 'ok', detail }, { actorId: answer.user.id, domainId })\n return answer\n } catch (error) {\n void recordAuditAction({ kind: 'auth.signin', outcome: 'error', code: errorCodeOf(error), detail }, { actorId: attempt.userId ?? null, domainId })\n throw error\n }\n}\n\nasync function signinOnce(attrs: { username: string; password: string }, context, attempt: SigninAttempt) {\n const { username } = attrs\n\n const repository = getRepository(User)\n\n var user = await repository.findOne({\n where: { username },\n relations: ['domains']\n })\n\n if (!user && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(username)) {\n user = await repository.findOne({\n where: {\n email: ILike(username)\n },\n relations: ['domains']\n })\n }\n\n if (!user)\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_NOT_FOUND\n })\n\n attempt.userId = user.id\n\n return await admitSignin(user, context, async () => {\n if (!User.verify(user.password, attrs.password, user.salt)) {\n user.failCount++\n if (user.failCount >= 5) {\n user.status = UserStatus.LOCKED\n /* 언제 풀리나를 계산할 수 있게 시각을 남긴다. 이 칸이 비면 판정 규칙이\n 잠금을 유지하는 쪽으로 기운다. */\n user.lockedAt = new Date()\n }\n await repository.save(user)\n if (user.status == UserStatus.LOCKED) {\n sendUnlockUserEmail({\n user,\n context\n })\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_LOCKED,\n detail: {\n username: user.username,\n email: user.email\n }\n })\n }\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.PASSWORD_NOT_MATCHED,\n detail: {\n username: user.username,\n email: user.email,\n failCount: user.failCount\n }\n })\n } else {\n user.failCount = 0\n await repository.save(user)\n }\n })\n}\n\n/**\n * The account gate every way of signing in goes through (ADR-0156 decision 4): deleted, locked -\n * and a lock that has run out lifts here - then the secret, then inactive. Only `verifySecret`\n * differs between a password and a passkey; a passkey arrives already verified and passes nothing.\n *\n * `user` must carry `domains`.\n */\nexport async function admitSignin(user: User, context, verifySecret: () => Promise<void>) {\n const { domain } = context?.state || {}\n const repository = getRepository(User)\n\n if (user.status == UserStatus.DELETED) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_DELETED\n })\n }\n\n if (user.status == UserStatus.LOCKED) {\n /* 시계는 한 번만 읽는다 — 두 번 읽으면 경계에 걸린 잠금이 두 판정 사이에서 갈린다. */\n const now = new Date()\n\n if (isAccountLocked(user, now, lockDurationMinutes())) {\n sendUnlockUserEmail({\n user,\n context\n })\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_LOCKED,\n detail: {\n username: user.username,\n email: user.email\n }\n })\n }\n\n /*\n * 시간이 지나 스스로 풀렸다 — 그 자리에서 행을 되돌리고 계속 진행한다. 주기 작업으로\n * 풀지 않는 이유는 `account-lock-rule.ts` 에 있다(계정이 10만이면 그것이 따로 일이 된다).\n *\n * `failCount` 도 함께 0 으로 둔다. 그래서 **잠금이 곧 실패 셈의 창**이 된다 — 이 계정이\n * 다시 잠기려면 지금부터 다섯 번을 틀려야 한다.\n */\n user.status = UserStatus.ACTIVATED\n user.failCount = 0\n user.lockedAt = null\n\n await repository.save(user)\n }\n\n await verifySecret()\n\n if (user.status == UserStatus.INACTIVE) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_NOT_ACTIVATED,\n detail: {\n username: user.username,\n email: user.email\n }\n })\n }\n\n return {\n user,\n token: await user.sign({ subdomain: domain?.subdomain }),\n domains: user.domains || []\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"signin.js","sourceRoot":"","sources":["../../server/controllers/signin.ts"],"names":[],"mappings":"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","sourcesContent":["import { ILike } from 'typeorm'\nimport { getRepository } from '@things-factory/shell'\n\nimport { sendUnlockUserEmail } from '../controllers/unlock-user.js'\nimport { AuthError } from '../errors/auth-error.js'\nimport { User, UserStatus } from '../service/user/user.js'\nimport { isAccountLocked } from '../utils/account-lock-rule.js'\nimport { lockDurationMinutes } from '../utils/lock-duration.js'\nimport { recordAuditAction } from '../service/audit/audit-recorder.js'\nimport { errorCodeOf } from '../service/audit/audit-target.js'\n\nexport type SigninMethod = 'password' | 'passkey'\n\nexport type SigninAttempt = { userId?: string }\n\n/**\n * A sign-in, and its audit (ADR-0141 §3.3): a success or a failure with its code. A failure is\n * written on its own and stays when the request fails (§4-3). An account that does not exist leaves\n * no actor - the name typed in is not a person.\n */\nexport async function signin(attrs: { username: string; password: string }, context?) {\n return await auditedSignin('password', context, attempt => signinOnce(attrs, context, attempt))\n}\n\n/**\n * The audit around one way of signing in. `run` puts the person in `attempt` as soon as it knows\n * who it is, so a failure after that names them. `detail.method` tells a password from a passkey\n * (ADR-0156 decision 4).\n */\nexport async function auditedSignin<T extends { user: User }>(\n method: SigninMethod,\n context,\n run: (attempt: SigninAttempt) => Promise<T>\n): Promise<T> {\n const attempt: SigninAttempt = {}\n const domainId = context?.state?.domain?.id ?? null\n const detail = { method }\n\n /*\n * Not awaited: the answer does not wait on its record, and a write that fails is counted, never\n * thrown (`recordAuditAction` without a transaction).\n */\n try {\n const answer = await run(attempt)\n void recordAuditAction({ kind: 'auth.signin', outcome: 'ok', detail }, { actorId: answer.user.id, domainId })\n return answer\n } catch (error) {\n void recordAuditAction({ kind: 'auth.signin', outcome: 'error', code: errorCodeOf(error), detail }, { actorId: attempt.userId ?? null, domainId })\n throw error\n }\n}\n\nasync function signinOnce(attrs: { username: string; password: string }, context, attempt: SigninAttempt) {\n const { username } = attrs\n\n const repository = getRepository(User)\n\n var user = await repository.findOne({\n where: { username },\n relations: ['domains']\n })\n\n if (!user && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(username)) {\n user = await repository.findOne({\n where: {\n email: ILike(username)\n },\n relations: ['domains']\n })\n }\n\n if (!user)\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_NOT_FOUND\n })\n\n attempt.userId = user.id\n\n return await admitSignin(user, context, async () => {\n if (!User.verify(user.password, attrs.password, user.salt)) {\n user.failCount++\n if (user.failCount >= 5) {\n user.status = UserStatus.LOCKED\n /* 언제 풀리나를 계산할 수 있게 시각을 남긴다. 이 칸이 비면 판정 규칙이\n 잠금을 유지하는 쪽으로 기운다. */\n user.lockedAt = new Date()\n }\n await repository.save(user)\n if (user.status == UserStatus.LOCKED) {\n sendUnlockUserEmail({\n user,\n context\n })\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_LOCKED,\n detail: {\n username: user.username,\n email: user.email\n }\n })\n }\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.PASSWORD_NOT_MATCHED,\n detail: {\n username: user.username,\n email: user.email,\n failCount: user.failCount\n }\n })\n } else {\n user.failCount = 0\n await repository.save(user)\n }\n })\n}\n\n/**\n * The account gate every way of signing in goes through (ADR-0156 decision 4): deleted, locked -\n * and a lock that has run out lifts here - then the secret, then inactive. Only `verifySecret`\n * differs between a password and a passkey; a passkey arrives already verified and passes nothing.\n *\n * `user` must carry `domains`.\n */\nexport async function admitSignin(user: User, context, verifySecret: () => Promise<void>) {\n const { domain } = context?.state || {}\n const repository = getRepository(User)\n\n if (user.status == UserStatus.DELETED) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_DELETED\n })\n }\n\n if (user.status == UserStatus.LOCKED) {\n /* 시계는 한 번만 읽는다 — 두 번 읽으면 경계에 걸린 잠금이 두 판정 사이에서 갈린다. */\n const now = new Date()\n\n if (isAccountLocked(user, now, lockDurationMinutes())) {\n sendUnlockUserEmail({\n user,\n context\n })\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_LOCKED,\n detail: {\n username: user.username,\n email: user.email\n }\n })\n }\n\n /*\n * 시간이 지나 스스로 풀렸다 — 그 자리에서 행을 되돌리고 계속 진행한다. 주기 작업으로\n * 풀지 않는 이유는 `account-lock-rule.ts` 에 있다(계정이 10만이면 그것이 따로 일이 된다).\n *\n * `failCount` 도 함께 0 으로 둔다. 그래서 **잠금이 곧 실패 셈의 창**이 된다 — 이 계정이\n * 다시 잠기려면 지금부터 다섯 번을 틀려야 한다.\n */\n user.status = UserStatus.ACTIVATED\n user.failCount = 0\n user.lockedAt = null\n\n await repository.save(user)\n }\n\n /*\n * Waiting for the password its person sets through a setup link (ADR-0161). Refused before the secret is looked at:\n * the password it carries is one nobody holds, so a guess against it is not a failure to count - and a passkey does\n * not open it either.\n */\n if (user.status == UserStatus.PWD_RESET_REQUIRED) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.PASSWORD_SETUP_PENDING\n })\n }\n\n await verifySecret()\n\n if (user.status == UserStatus.INACTIVE) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_NOT_ACTIVATED,\n detail: {\n username: user.username,\n email: user.email\n }\n })\n }\n\n return {\n user,\n token: await user.sign({ subdomain: domain?.subdomain }),\n domains: user.domains || []\n }\n}\n"]}
|
package/dist-server/index.d.ts
CHANGED
|
@@ -19,5 +19,6 @@ export * from './utils/encrypt-state.js';
|
|
|
19
19
|
export * from './utils/check-permission.js';
|
|
20
20
|
export * from './utils/check-user-has-role.js';
|
|
21
21
|
export * from './utils/build-auth-url.js';
|
|
22
|
+
export * from './controllers/password-setup.js';
|
|
22
23
|
export * from './errors/index.js';
|
|
23
24
|
export * from './types.js';
|
package/dist-server/index.js
CHANGED
|
@@ -26,6 +26,8 @@ tslib_1.__exportStar(require("./utils/encrypt-state.js"), exports);
|
|
|
26
26
|
tslib_1.__exportStar(require("./utils/check-permission.js"), exports);
|
|
27
27
|
tslib_1.__exportStar(require("./utils/check-user-has-role.js"), exports);
|
|
28
28
|
tslib_1.__exportStar(require("./utils/build-auth-url.js"), exports);
|
|
29
|
+
/* A waiting account and its setup link (ADR-0161) - for a module that makes accounts, as contact does. */
|
|
30
|
+
tslib_1.__exportStar(require("./controllers/password-setup.js"), exports);
|
|
29
31
|
tslib_1.__exportStar(require("./errors/index.js"), exports);
|
|
30
32
|
tslib_1.__exportStar(require("./types.js"), exports);
|
|
31
33
|
process.on('bootstrap-module-start', async ({ app, config, client }) => {
|
package/dist-server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,6EAA6E;AAC7E,6EAA0E;AAC1E,+EAAsF;AACtF,2EAAwE;AAExE,uBAAoB;AAEpB,6DAAkC;AAClC,uGAAuG;AACvG,6EAAkD;AAClD,yEAA8C;AAE9C,gEAAqC;AAErC,iEAAsC;AACtC,4DAAiC;AACjC,sDAA2B;AAE3B,sEAA2C;AAC3C,sEAA2C;AAC3C,yEAA8C;AAC9C,6EAAkD;AAClD,sEAA2C;AAC3C,gEAAqC;AACrC,+EAAoD;AACpD,yEAA8C;AAC9C,mEAAwC;AACxC,sEAA2C;AAC3C,yEAA8C;AAC9C,oEAAyC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,6EAA6E;AAC7E,6EAA0E;AAC1E,+EAAsF;AACtF,2EAAwE;AAExE,uBAAoB;AAEpB,6DAAkC;AAClC,uGAAuG;AACvG,6EAAkD;AAClD,yEAA8C;AAE9C,gEAAqC;AAErC,iEAAsC;AACtC,4DAAiC;AACjC,sDAA2B;AAE3B,sEAA2C;AAC3C,sEAA2C;AAC3C,yEAA8C;AAC9C,6EAAkD;AAClD,sEAA2C;AAC3C,gEAAqC;AACrC,+EAAoD;AACpD,yEAA8C;AAC9C,mEAAwC;AACxC,sEAA2C;AAC3C,yEAA8C;AAC9C,oEAAyC;AACzC,0GAA0G;AAC1G,0EAA+C;AAE/C,4DAAiC;AAEjC,qDAA0B;AAE1B,OAAO,CAAC,EAAE,CAAC,wBAA+B,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAO,EAAE,EAAE;IACjF,MAAM,IAAA,yCAAqB,EAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAqC,CAAC,CAAC,CAAA;IAErG,OAAO,CAAC,GAAG,CAAC,2EAA2E,CAAC,CAAA;IAExF;;;;;OAKG;IACH,IAAA,8CAA2B,GAAE,CAAA;IAE7B;;;;;OAKG;IACH,IAAA,sCAAkB,GAAE,CAAA;IAEpB,kFAAkF;IAClF,IAAA,wCAAmB,GAAE,CAAA;AACvB,CAAC,CAAC,CAAA;AAEF,uEAAuE;AACvE,mDAA4C;AAE5C,KAAK,MAAM,IAAI,IAAI,0BAAU;IAAE,IAAA,+BAAgB,EAAC,IAAI,CAAC,CAAA","sourcesContent":["import { synchronizePrivileges } from './service/privilege/privilege-sync.js'\nimport { reportDeclaredAxes } from './service/privilege/privilege-axis.js'\nimport { reportUnknownTemplateGrants } from './service/role-template/role-template.js'\nimport { startAuditRetention } from './service/audit/audit-retention.js'\n\nimport './routes.js'\n\nexport * from './service/index.js'\n/* AI 입력 declarations - plain data, registered by an application that runs the assistant (ADR-0103). */\nexport * from './ai-entry/access-entry-targets.js'\nexport * from './ai-entry/access-assistant.js'\nexport type { EntryTargetDeclaration as AuthEntryTarget } from './ai-entry/entry-target-shape.js'\nexport * from './migrations/index.js'\n\nexport * from './middlewares/index.js'\nexport * from './router/index.js'\nexport * from './routes.js'\n\nexport * from './utils/get-domain-users.js'\nexport * from './utils/get-user-domains.js'\nexport * from './utils/checkin-domain-rule.js'\nexport * from './utils/can-user-checkin-domain.js'\nexport * from './utils/domain-ownership.js'\nexport * from './utils/get-secret.js'\nexport * from './utils/check-user-belongs-domain.js'\nexport * from './utils/access-token-cookie.js'\nexport * from './utils/encrypt-state.js'\nexport * from './utils/check-permission.js'\nexport * from './utils/check-user-has-role.js'\nexport * from './utils/build-auth-url.js'\n/* A waiting account and its setup link (ADR-0161) - for a module that makes accounts, as contact does. */\nexport * from './controllers/password-setup.js'\n\nexport * from './errors/index.js'\n\nexport * from './types.js'\n\nprocess.on('bootstrap-module-start' as any, async ({ app, config, client }: any) => {\n await synchronizePrivileges(Object.values(process['PRIVILEGES'] as Record<string, [string, string]>))\n\n console.log('[auth-base:bootstrap] Synchronization for privilege master has just done.')\n\n /*\n * Templates name privileges by (category, axis), and a category can move. A template\n * that survives a rename becomes a role that grants nothing — the administrator picks\n * \"quality inspector\", gets a role, and no door opens. Check the names once, here, where\n * the privilege registry has just been filled.\n */\n reportUnknownTemplateGrants()\n\n /*\n * Say which axis vocabulary this installation ended up with. It cannot go in the SDL —\n * it differs per installation, which is the whole point — so a boot line is the honest\n * substitute, and it separates \"nobody declared, these are the defaults\" from \"someone\n * declared exactly these\".\n */\n reportDeclaredAxes()\n\n /* Audit rows past the retention go, once a day, on one instance (ADR-0141 §7). */\n startAuditRetention()\n})\n\nimport { registerRoleTask } from './service/role-template/role-task.js'\nimport { ROLE_TASKS } from './role-tasks.js'\n\nfor (const task of ROLE_TASKS) registerRoleTask(task)\n"]}
|
|
@@ -5,11 +5,7 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const koa_passport_1 = tslib_1.__importDefault(require("koa-passport"));
|
|
6
6
|
const passport_jwt_1 = require("passport-jwt");
|
|
7
7
|
const env_1 = require("@things-factory/env");
|
|
8
|
-
const reset_password_js_1 = require("../controllers/reset-password.js");
|
|
9
|
-
const make_verification_token_js_1 = require("../controllers/utils/make-verification-token.js");
|
|
10
|
-
const save_verification_token_js_1 = require("../controllers/utils/save-verification-token.js");
|
|
11
8
|
const user_js_1 = require("../service/user/user.js");
|
|
12
|
-
const verification_token_js_1 = require("../service/verification-token/verification-token.js");
|
|
13
9
|
const access_token_cookie_js_1 = require("../utils/access-token-cookie.js");
|
|
14
10
|
const get_secret_js_1 = require("../utils/get-secret.js");
|
|
15
11
|
const sessionExpiryPolicy = env_1.config.get('session/expiryPolicy', 'fixed');
|
|
@@ -49,29 +45,16 @@ async function jwtAuthenticateMiddleware(context, next) {
|
|
|
49
45
|
context.throw(401, e.message);
|
|
50
46
|
}
|
|
51
47
|
else {
|
|
48
|
+
/* A waiting account (ADR-0161) is refused in `checkAuth`, with every other status that opens nothing. */
|
|
52
49
|
const userEntity = await user_js_1.User.checkAuth(decoded);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
context.redirect(`${reset_password_js_1.RESET_PASSWORD_PATH}#${token}`);
|
|
60
|
-
}
|
|
61
|
-
catch (e) {
|
|
62
|
-
throw err;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
context.state.user = userEntity;
|
|
67
|
-
context.state.decodedToken = decoded;
|
|
68
|
-
if (sessionExpiryPolicy == 'rolling') {
|
|
69
|
-
/* To renew the expiry time on each request, a token is issued and the session is updated. */
|
|
70
|
-
const token = await userEntity.sign();
|
|
71
|
-
(0, access_token_cookie_js_1.setAccessTokenCookie)(context, token);
|
|
72
|
-
}
|
|
73
|
-
await next();
|
|
50
|
+
context.state.user = userEntity;
|
|
51
|
+
context.state.decodedToken = decoded;
|
|
52
|
+
if (sessionExpiryPolicy == 'rolling') {
|
|
53
|
+
/* To renew the expiry time on each request, a token is issued and the session is updated. */
|
|
54
|
+
const token = await userEntity.sign();
|
|
55
|
+
(0, access_token_cookie_js_1.setAccessTokenCookie)(context, token);
|
|
74
56
|
}
|
|
57
|
+
await next();
|
|
75
58
|
}
|
|
76
59
|
})(context, next);
|
|
77
60
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt-authenticate-middleware.js","sourceRoot":"","sources":["../../server/middlewares/jwt-authenticate-middleware.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"jwt-authenticate-middleware.js","sourceRoot":"","sources":["../../server/middlewares/jwt-authenticate-middleware.ts"],"names":[],"mappings":";;AAuCA,8DA+BC;;AAtED,wEAAmC;AACnC,+CAAkE;AAElE,6CAA4C;AAE5C,qDAA8C;AAC9C,4EAAoH;AACpH,0DAA+C;AAE/C,MAAM,mBAAmB,GAAG,YAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAA;AAEvE,sBAAQ,CAAC,GAAG,CACV,IAAI,uBAAW,CACb;IACE,WAAW,EAAE,sBAAM;IACnB,iBAAiB,EAAE,IAAI;IACvB,cAAc,EAAE,yBAAU,CAAC,cAAc,CAAC;QACxC,yBAAU,CAAC,2BAA2B,EAAE;QACxC,yBAAU,CAAC,UAAU,CAAC,eAAe,CAAC;QACtC,yBAAU,CAAC,UAAU,CAAC,gBAAgB,CAAC;QACvC,yBAAU,CAAC,qBAAqB,CAAC,cAAc,CAAC;QAChD,yBAAU,CAAC,aAAa,CAAC,cAAc,CAAC;QACxC,GAAG,CAAC,EAAE;YACJ,IAAI,KAAK,GAAG,IAAI,CAAA;YAChB,KAAK,GAAG,IAAA,6CAAoB,EAAC,GAAG,EAAE,GAAG,CAAC,CAAA;YACtC,OAAO,KAAK,CAAA;QACd,CAAC;KACF,CAAC;CACH,EACD,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;IAC/B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC5B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;IACpB,CAAC;AACH,CAAC,CACF,CACF,CAAA;AAEM,KAAK,UAAU,yBAAyB,CAAC,OAAO,EAAE,IAAI;IAC3D,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IACxB,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAC9B,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,MAAM,IAAI,EAAE,CAAA;IACrB,CAAC;IAED,OAAO,MAAM,sBAAQ,CAAC,YAAY,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QACzF,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,IAAI,IAAI,CAAC,CAAA;YAE7C,IAAA,+CAAsB,EAAC,OAAO,CAAC,CAAA;YAE/B,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;QAC/B,CAAC;aAAM,CAAC;YACN,yGAAyG;YACzG,MAAM,UAAU,GAAG,MAAM,cAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;YAEhD,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,UAAU,CAAA;YAC/B,OAAO,CAAC,KAAK,CAAC,YAAY,GAAG,OAAO,CAAA;YAEpC,IAAI,mBAAmB,IAAI,SAAS,EAAE,CAAC;gBACrC,6FAA6F;gBAE7F,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,CAAA;gBACrC,IAAA,6CAAoB,EAAC,OAAO,EAAE,KAAK,CAAC,CAAA;YACtC,CAAC;YAED,MAAM,IAAI,EAAE,CAAA;QACd,CAAC;IACH,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;AACnB,CAAC","sourcesContent":["import passport from 'koa-passport'\nimport { ExtractJwt, Strategy as JWTstrategy } from 'passport-jwt'\n\nimport { config } from '@things-factory/env'\n\nimport { User } from '../service/user/user.js'\nimport { clearAccessTokenCookie, getAccessTokenCookie, setAccessTokenCookie } from '../utils/access-token-cookie.js'\nimport { SECRET } from '../utils/get-secret.js'\n\nconst sessionExpiryPolicy = config.get('session/expiryPolicy', 'fixed')\n\npassport.use(\n new JWTstrategy(\n {\n secretOrKey: SECRET,\n passReqToCallback: true,\n jwtFromRequest: ExtractJwt.fromExtractors([\n ExtractJwt.fromAuthHeaderAsBearerToken(),\n ExtractJwt.fromHeader('authorization'),\n ExtractJwt.fromHeader('x-access-token'),\n ExtractJwt.fromUrlQueryParameter('access_token'),\n ExtractJwt.fromBodyField('access_token'),\n req => {\n var token = null\n token = getAccessTokenCookie(req?.ctx)\n return token\n }\n ])\n },\n async (request, decoded, done) => {\n try {\n return done(null, decoded)\n } catch (error) {\n return done(error)\n }\n }\n )\n)\n\nexport async function jwtAuthenticateMiddleware(context, next) {\n const { path } = context\n const { user } = context.state\n if (user) {\n return await next()\n }\n\n return await passport.authenticate('jwt', { session: false }, async (err, decoded, info) => {\n if (err || !decoded) {\n const e = (context.state.error = err || info)\n\n clearAccessTokenCookie(context)\n\n context.throw(401, e.message)\n } else {\n /* A waiting account (ADR-0161) is refused in `checkAuth`, with every other status that opens nothing. */\n const userEntity = await User.checkAuth(decoded)\n\n context.state.user = userEntity\n context.state.decodedToken = decoded\n\n if (sessionExpiryPolicy == 'rolling') {\n /* To renew the expiry time on each request, a token is issued and the session is updated. */\n\n const token = await userEntity.sign()\n setAccessTokenCookie(context, token)\n }\n\n await next()\n }\n })(context, next)\n}\n"]}
|
|
@@ -6,7 +6,6 @@ const koa_router_1 = tslib_1.__importDefault(require("koa-router"));
|
|
|
6
6
|
const typeorm_1 = require("typeorm");
|
|
7
7
|
const env_1 = require("@things-factory/env");
|
|
8
8
|
const shell_1 = require("@things-factory/shell");
|
|
9
|
-
const invitation_js_1 = require("../controllers/invitation.js");
|
|
10
9
|
const reset_password_js_1 = require("../controllers/reset-password.js");
|
|
11
10
|
const unlock_user_js_1 = require("../controllers/unlock-user.js");
|
|
12
11
|
const verification_js_1 = require("../controllers/verification.js");
|
|
@@ -176,43 +175,6 @@ exports.authPublicProcessRouter.post('/resend-verification-email', async (contex
|
|
|
176
175
|
context.status = 200;
|
|
177
176
|
context.body = t('text.verification email sent');
|
|
178
177
|
});
|
|
179
|
-
/*
|
|
180
|
-
* ── The invitation page ─────────────────────────────────────────────────────
|
|
181
|
-
* Public on purpose: the person following this link is not a member of the domain yet, which is
|
|
182
|
-
* the entire point of an invitation. The old link went to `/auth/accept/<token>`, a path with no
|
|
183
|
-
* route on any router, so it fell through to the private history-fallback — and the domain gate
|
|
184
|
-
* there turned the invitee away for not being a member. It asked somebody trying to join to
|
|
185
|
-
* already have joined.
|
|
186
|
-
*
|
|
187
|
-
* ── Why the token is not in this path ───────────────────────────────────────
|
|
188
|
-
* It rides in the link's fragment (`#<token>`), which browsers do not send. So the request line
|
|
189
|
-
* here carries nothing, and the token stays out of access logs, proxy logs and `Referer`. The
|
|
190
|
-
* page reads it from the fragment, clears it from the address bar, and posts it below.
|
|
191
|
-
*/
|
|
192
|
-
exports.authPublicProcessRouter.get('/accept-invitation', async (context, next) => {
|
|
193
|
-
await context.render('auth-page', {
|
|
194
|
-
pageElement: 'auth-accept-invitation',
|
|
195
|
-
elementScript: '/auth/accept-invitation.js',
|
|
196
|
-
data: {
|
|
197
|
-
disableUserFavoredLanguage,
|
|
198
|
-
languages
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
/*
|
|
203
|
-
* The address is asked for as well as the token. A forwarded mail is the one way the token can
|
|
204
|
-
* still travel, and nothing about where it sits can prevent that — so holding the link is not
|
|
205
|
-
* enough on its own.
|
|
206
|
-
*/
|
|
207
|
-
exports.authPublicProcessRouter.post('/accept-invitation', async (context, next) => {
|
|
208
|
-
const { token, email, name, password } = context.request.body || {};
|
|
209
|
-
const { domain, created } = await (0, invitation_js_1.acceptInvitation)({ token, email, name, password, context });
|
|
210
|
-
context.body = {
|
|
211
|
-
subdomain: domain?.subdomain,
|
|
212
|
-
created,
|
|
213
|
-
redirectTo: `/auth/signin?redirect_to=${encodeURIComponent('/')}`
|
|
214
|
-
};
|
|
215
|
-
});
|
|
216
178
|
exports.authPublicProcessRouter.post('/forgot-password', async (context, next) => {
|
|
217
179
|
const { t } = context;
|
|
218
180
|
const { email } = context.request.body;
|
|
@@ -234,9 +196,9 @@ exports.authPublicProcessRouter.post('/forgot-password', async (context, next) =
|
|
|
234
196
|
});
|
|
235
197
|
exports.authPublicProcessRouter.post('/reset-password', async (context, next) => {
|
|
236
198
|
const { header, t } = context;
|
|
237
|
-
const { password, token } = context.request.body;
|
|
199
|
+
const { password, token, email } = context.request.body;
|
|
238
200
|
try {
|
|
239
|
-
if (!(token && password)) {
|
|
201
|
+
if (!(token && password && email)) {
|
|
240
202
|
let message = t('error.token or password is invalid');
|
|
241
203
|
context.status = 404;
|
|
242
204
|
context.body = {
|
|
@@ -258,7 +220,7 @@ exports.authPublicProcessRouter.post('/reset-password', async (context, next) =>
|
|
|
258
220
|
}
|
|
259
221
|
return;
|
|
260
222
|
}
|
|
261
|
-
await (0, reset_password_js_1.resetPassword)(token, password, context);
|
|
223
|
+
await (0, reset_password_js_1.resetPassword)(token, password, email, context);
|
|
262
224
|
var message = t('text.password changed successfully');
|
|
263
225
|
context.body = message;
|
|
264
226
|
(0, access_token_cookie_js_1.clearAccessTokenCookie)(context);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-public-process-router.js","sourceRoot":"","sources":["../../server/router/auth-public-process-router.ts"],"names":[],"mappings":"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","sourcesContent":["import Router from 'koa-router'\nimport { ILike } from 'typeorm'\n\nimport { config } from '@things-factory/env'\nimport { getRepository, getSiteRootPath } from '@things-factory/shell'\n\nimport { acceptInvitation } from '../controllers/invitation.js'\nimport { resetPassword, sendPasswordResetEmail } from '../controllers/reset-password.js'\nimport { unlockUser } from '../controllers/unlock-user.js'\nimport { resendVerificationEmail, verify } from '../controllers/verification.js'\nimport { User } from '../service/user/user.js'\nimport { accepts } from '../utils/accepts.js'\nimport { recordAuditAction } from '../service/audit/audit-recorder.js'\nimport { findAuth } from './site-root-router.js'\nimport { clearAccessTokenCookie } from '../utils/access-token-cookie.js'\n\nconst disableUserSignupProcess = config.get('disableUserSignupProcess', false)\nconst disableUserFavoredLanguage = config.get('i18n/disableUserFavoredLanguage', false)\nconst languages = config.get('i18n/languages', false)\nconst passwordRule = config.get('password') || {\n lowerCase: true,\n upperCase: true,\n digit: true,\n specialCharacter: true,\n allowRepeat: false,\n useTightPattern: true,\n useLoosePattern: false,\n tightCharacterLength: 8,\n looseCharacterLength: 15\n}\n\nexport const authPublicProcessRouter = new Router({\n prefix: '/auth'\n})\n\nauthPublicProcessRouter.post('/join', async (context, next) => {\n const { email } = context.request.body || {}\n\n const repository = getRepository(User)\n\n var user = await repository.findOne({\n where: { username: email },\n relations: ['domains']\n })\n\n if (!user && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)) {\n user = await repository.findOne({\n where: { email: ILike(email) },\n relations: ['domains']\n })\n }\n\n if (user) {\n context.redirect(`/auth/signin?username=${email}`)\n } else {\n context.redirect(`/auth/signup?username=${email}&email=${email}`)\n }\n})\n\nauthPublicProcessRouter.all('/signout', findAuth, async (context, next) => {\n const { header, t } = context\n clearAccessTokenCookie(context)\n\n /* After the cookie is gone: an audit failure never keeps anyone signed in (ADR-0141 §4-2). */\n if (context.state.user) {\n await recordAuditAction({ kind: 'auth.signout', outcome: 'ok' }, { domainId: null })\n }\n\n context.body = t('text.signout successfully')\n\n if (accepts(header.accept, ['text/html', '*/*'])) {\n context.redirect(getSiteRootPath(context))\n }\n})\n\nauthPublicProcessRouter.get('/forgot-password', async (context, next) => {\n const { email } = context.request.query\n\n await context.render('auth-page', {\n pageElement: 'forgot-password',\n elementScript: '/auth/forgot-password.js',\n data: {\n email,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n})\n\n/*\n * The mailed link is `/auth/reset-password#<token>` (and the same shape for unlock and verify). The\n * page reads the token from the fragment; this request never carries it, so nothing here reads one.\n */\nauthPublicProcessRouter.get('/reset-password', async (context, next) => {\n await context.render('auth-page', {\n pageElement: 'reset-password',\n elementScript: '/auth/reset-password.js',\n data: {\n passwordRule,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n})\n\nauthPublicProcessRouter.get('/unlock-user', async (context, next) => {\n await context.render('auth-page', {\n pageElement: 'unlock-user',\n elementScript: '/auth/unlock-user.js',\n data: {\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n})\n\nauthPublicProcessRouter.get('/activate/:email', async (context, next) => {\n const { email } = context.params\n\n await context.render('auth-page', {\n pageElement: 'auth-activate',\n elementScript: '/auth/activate.js',\n data: {\n email,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n})\n\n/*\n * Confirming an address is a POST from the page, not the GET of the link. The link used to be\n * `/auth/verify/<token>` and opening it confirmed — so did any mail scanner that fetched it, and the\n * token sat in every access log on the way.\n */\nauthPublicProcessRouter.get('/verify', async (context, next) => {\n await context.render('auth-page', {\n pageElement: 'auth-verify',\n elementScript: '/auth/verify.js',\n data: {\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n})\n\nauthPublicProcessRouter.post('/verify', async (context, next) => {\n const { header, t } = context\n const { token } = context.request.body || {}\n\n let message: string\n\n try {\n await verify(token)\n message = t('text.user activated successfully')\n } catch (e) {\n context.status = 404\n message = t('text.invalid verification token')\n }\n\n context.body = message\n\n if (accepts(header.accept, ['text/html', '*/*'])) {\n await context.render('auth-page', {\n pageElement: 'auth-result',\n elementScript: '/auth/result.js',\n data: {\n message,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n }\n})\n\n/*\n * This and forgot-password give one answer whether or not the address has an account to mail - a\n * different one would tell anyone which addresses are registered. Only a mail that was due and did\n * not go is a failure. Both used to set nothing in those cases, and koa answered a bare 404 that the\n * page showed as \"Not Found\".\n */\nauthPublicProcessRouter.post('/resend-verification-email', async (context, next) => {\n const { t } = context\n const { email } = context.request.body || {}\n\n if ((await resendVerificationEmail(email, context)) == 'failed') {\n context.status = 503\n context.body = t('error.mail could not be sent')\n return\n }\n\n context.status = 200\n context.body = t('text.verification email sent')\n})\n\n/*\n * ── The invitation page ─────────────────────────────────────────────────────\n * Public on purpose: the person following this link is not a member of the domain yet, which is\n * the entire point of an invitation. The old link went to `/auth/accept/<token>`, a path with no\n * route on any router, so it fell through to the private history-fallback — and the domain gate\n * there turned the invitee away for not being a member. It asked somebody trying to join to\n * already have joined.\n *\n * ── Why the token is not in this path ───────────────────────────────────────\n * It rides in the link's fragment (`#<token>`), which browsers do not send. So the request line\n * here carries nothing, and the token stays out of access logs, proxy logs and `Referer`. The\n * page reads it from the fragment, clears it from the address bar, and posts it below.\n */\nauthPublicProcessRouter.get('/accept-invitation', async (context, next) => {\n await context.render('auth-page', {\n pageElement: 'auth-accept-invitation',\n elementScript: '/auth/accept-invitation.js',\n data: {\n disableUserFavoredLanguage,\n languages\n }\n })\n})\n\n/*\n * The address is asked for as well as the token. A forwarded mail is the one way the token can\n * still travel, and nothing about where it sits can prevent that — so holding the link is not\n * enough on its own.\n */\nauthPublicProcessRouter.post('/accept-invitation', async (context, next) => {\n const { token, email, name, password } = context.request.body || ({} as any)\n\n const { domain, created } = await acceptInvitation({ token, email, name, password, context })\n\n context.body = {\n subdomain: domain?.subdomain,\n created,\n redirectTo: `/auth/signin?redirect_to=${encodeURIComponent('/')}`\n }\n})\n\nauthPublicProcessRouter.post('/forgot-password', async (context, next) => {\n const { t } = context\n const { email } = context.request.body\n\n if (!email) return next()\n\n const userRepo = getRepository(User)\n const user = await userRepo.findOne({\n where: {\n email\n }\n })\n\n if (user && !(await sendPasswordResetEmail({ user, context }))) {\n context.status = 503\n context.body = t('error.mail could not be sent')\n return\n }\n\n context.status = 200\n context.body = t('text.password reset email sent')\n})\n\nauthPublicProcessRouter.post('/reset-password', async (context, next) => {\n const { header, t } = context\n const { password, token } = context.request.body\n\n try {\n if (!(token && password)) {\n let message = t('error.token or password is invalid')\n\n context.status = 404\n context.body = {\n message\n }\n\n if (accepts(header.accept, ['text/html', '*/*'])) {\n await context.render('auth-page', {\n pageElement: 'reset-password',\n elementScript: '/auth/reset-password.js',\n data: {\n token,\n message,\n passwordRule,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n }\n\n return\n }\n\n await resetPassword(token, password, context)\n\n var message = t('text.password changed successfully')\n context.body = message\n\n clearAccessTokenCookie(context)\n\n if (accepts(header.accept, ['text/html', '*/*'])) {\n await context.render('auth-page', {\n pageElement: 'auth-result',\n elementScript: '/auth/result.js',\n data: {\n message,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n }\n } catch (e) {\n context.status = 404\n context.body = e.message\n\n if (accepts(header.accept, ['text/html', '*/*'])) {\n await context.render('auth-page', {\n pageElement: 'reset-password',\n elementScript: '/auth/reset-password.js',\n data: {\n token,\n message: e.message,\n passwordRule,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n }\n }\n})\n\nauthPublicProcessRouter.post('/unlock-user', async (context, next) => {\n const { header, t } = context\n const { password, token } = context.request.body\n\n /* Both are required, the way `/auth/reset-password` requires them. This read `||`, so a body\n carrying a password and no token passed the guard and reached the unlock with no token —\n see the note in `unlockUser`. */\n if (!(token && password)) {\n context.status = 404\n context.body = t('error.token or password is invalid')\n\n return\n }\n\n const succeed = await unlockUser(token, password)\n\n /*\n * Say what happened. The success page used to render whether or not the unlock took, so a\n * reused link, a token for another account, or an account that was never locked all read as\n * \"account is reactivated\" — and the person then could not sign in.\n *\n * A refusal returns to the form with a reason, the shape `/auth/reset-password` already uses.\n */\n if (!succeed) {\n const message = t('text.invalid verification token')\n\n context.status = 404\n context.body = message\n\n if (accepts(header.accept, ['text/html', '*/*'])) {\n await context.render('auth-page', {\n pageElement: 'unlock-user',\n elementScript: '/auth/unlock-user.js',\n data: {\n token,\n message,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n }\n\n return\n }\n\n context.body = t('text.password reset succeed')\n\n clearAccessTokenCookie(context)\n\n if (accepts(header.accept, ['text/html', '*/*'])) {\n await context.render('auth-page', {\n pageElement: 'auth-result',\n elementScript: '/auth/result.js',\n data: {\n message: t('text.account is reactivated'),\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n }\n})\n"]}
|
|
1
|
+
{"version":3,"file":"auth-public-process-router.js","sourceRoot":"","sources":["../../server/router/auth-public-process-router.ts"],"names":[],"mappings":"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","sourcesContent":["import Router from 'koa-router'\nimport { ILike } from 'typeorm'\n\nimport { config } from '@things-factory/env'\nimport { getRepository, getSiteRootPath } from '@things-factory/shell'\n\nimport { resetPassword, sendPasswordResetEmail } from '../controllers/reset-password.js'\nimport { unlockUser } from '../controllers/unlock-user.js'\nimport { resendVerificationEmail, verify } from '../controllers/verification.js'\nimport { User } from '../service/user/user.js'\nimport { accepts } from '../utils/accepts.js'\nimport { recordAuditAction } from '../service/audit/audit-recorder.js'\nimport { findAuth } from './site-root-router.js'\nimport { clearAccessTokenCookie } from '../utils/access-token-cookie.js'\n\nconst disableUserSignupProcess = config.get('disableUserSignupProcess', false)\nconst disableUserFavoredLanguage = config.get('i18n/disableUserFavoredLanguage', false)\nconst languages = config.get('i18n/languages', false)\nconst passwordRule = config.get('password') || {\n lowerCase: true,\n upperCase: true,\n digit: true,\n specialCharacter: true,\n allowRepeat: false,\n useTightPattern: true,\n useLoosePattern: false,\n tightCharacterLength: 8,\n looseCharacterLength: 15\n}\n\nexport const authPublicProcessRouter = new Router({\n prefix: '/auth'\n})\n\nauthPublicProcessRouter.post('/join', async (context, next) => {\n const { email } = context.request.body || {}\n\n const repository = getRepository(User)\n\n var user = await repository.findOne({\n where: { username: email },\n relations: ['domains']\n })\n\n if (!user && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)) {\n user = await repository.findOne({\n where: { email: ILike(email) },\n relations: ['domains']\n })\n }\n\n if (user) {\n context.redirect(`/auth/signin?username=${email}`)\n } else {\n context.redirect(`/auth/signup?username=${email}&email=${email}`)\n }\n})\n\nauthPublicProcessRouter.all('/signout', findAuth, async (context, next) => {\n const { header, t } = context\n clearAccessTokenCookie(context)\n\n /* After the cookie is gone: an audit failure never keeps anyone signed in (ADR-0141 §4-2). */\n if (context.state.user) {\n await recordAuditAction({ kind: 'auth.signout', outcome: 'ok' }, { domainId: null })\n }\n\n context.body = t('text.signout successfully')\n\n if (accepts(header.accept, ['text/html', '*/*'])) {\n context.redirect(getSiteRootPath(context))\n }\n})\n\nauthPublicProcessRouter.get('/forgot-password', async (context, next) => {\n const { email } = context.request.query\n\n await context.render('auth-page', {\n pageElement: 'forgot-password',\n elementScript: '/auth/forgot-password.js',\n data: {\n email,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n})\n\n/*\n * The mailed link is `/auth/reset-password#<token>` (and the same shape for unlock and verify). The\n * page reads the token from the fragment; this request never carries it, so nothing here reads one.\n */\nauthPublicProcessRouter.get('/reset-password', async (context, next) => {\n await context.render('auth-page', {\n pageElement: 'reset-password',\n elementScript: '/auth/reset-password.js',\n data: {\n passwordRule,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n})\n\nauthPublicProcessRouter.get('/unlock-user', async (context, next) => {\n await context.render('auth-page', {\n pageElement: 'unlock-user',\n elementScript: '/auth/unlock-user.js',\n data: {\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n})\n\nauthPublicProcessRouter.get('/activate/:email', async (context, next) => {\n const { email } = context.params\n\n await context.render('auth-page', {\n pageElement: 'auth-activate',\n elementScript: '/auth/activate.js',\n data: {\n email,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n})\n\n/*\n * Confirming an address is a POST from the page, not the GET of the link. The link used to be\n * `/auth/verify/<token>` and opening it confirmed — so did any mail scanner that fetched it, and the\n * token sat in every access log on the way.\n */\nauthPublicProcessRouter.get('/verify', async (context, next) => {\n await context.render('auth-page', {\n pageElement: 'auth-verify',\n elementScript: '/auth/verify.js',\n data: {\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n})\n\nauthPublicProcessRouter.post('/verify', async (context, next) => {\n const { header, t } = context\n const { token } = context.request.body || {}\n\n let message: string\n\n try {\n await verify(token)\n message = t('text.user activated successfully')\n } catch (e) {\n context.status = 404\n message = t('text.invalid verification token')\n }\n\n context.body = message\n\n if (accepts(header.accept, ['text/html', '*/*'])) {\n await context.render('auth-page', {\n pageElement: 'auth-result',\n elementScript: '/auth/result.js',\n data: {\n message,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n }\n})\n\n/*\n * This and forgot-password give one answer whether or not the address has an account to mail - a\n * different one would tell anyone which addresses are registered. Only a mail that was due and did\n * not go is a failure. Both used to set nothing in those cases, and koa answered a bare 404 that the\n * page showed as \"Not Found\".\n */\nauthPublicProcessRouter.post('/resend-verification-email', async (context, next) => {\n const { t } = context\n const { email } = context.request.body || {}\n\n if ((await resendVerificationEmail(email, context)) == 'failed') {\n context.status = 503\n context.body = t('error.mail could not be sent')\n return\n }\n\n context.status = 200\n context.body = t('text.verification email sent')\n})\n\nauthPublicProcessRouter.post('/forgot-password', async (context, next) => {\n const { t } = context\n const { email } = context.request.body\n\n if (!email) return next()\n\n const userRepo = getRepository(User)\n const user = await userRepo.findOne({\n where: {\n email\n }\n })\n\n if (user && !(await sendPasswordResetEmail({ user, context }))) {\n context.status = 503\n context.body = t('error.mail could not be sent')\n return\n }\n\n context.status = 200\n context.body = t('text.password reset email sent')\n})\n\nauthPublicProcessRouter.post('/reset-password', async (context, next) => {\n const { header, t } = context\n const { password, token, email } = context.request.body\n\n try {\n if (!(token && password && email)) {\n let message = t('error.token or password is invalid')\n\n context.status = 404\n context.body = {\n message\n }\n\n if (accepts(header.accept, ['text/html', '*/*'])) {\n await context.render('auth-page', {\n pageElement: 'reset-password',\n elementScript: '/auth/reset-password.js',\n data: {\n token,\n message,\n passwordRule,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n }\n\n return\n }\n\n await resetPassword(token, password, email, context)\n\n var message = t('text.password changed successfully')\n context.body = message\n\n clearAccessTokenCookie(context)\n\n if (accepts(header.accept, ['text/html', '*/*'])) {\n await context.render('auth-page', {\n pageElement: 'auth-result',\n elementScript: '/auth/result.js',\n data: {\n message,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n }\n } catch (e) {\n context.status = 404\n context.body = e.message\n\n if (accepts(header.accept, ['text/html', '*/*'])) {\n await context.render('auth-page', {\n pageElement: 'reset-password',\n elementScript: '/auth/reset-password.js',\n data: {\n token,\n message: e.message,\n passwordRule,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n }\n }\n})\n\nauthPublicProcessRouter.post('/unlock-user', async (context, next) => {\n const { header, t } = context\n const { password, token } = context.request.body\n\n /* Both are required, the way `/auth/reset-password` requires them. This read `||`, so a body\n carrying a password and no token passed the guard and reached the unlock with no token —\n see the note in `unlockUser`. */\n if (!(token && password)) {\n context.status = 404\n context.body = t('error.token or password is invalid')\n\n return\n }\n\n const succeed = await unlockUser(token, password)\n\n /*\n * Say what happened. The success page used to render whether or not the unlock took, so a\n * reused link, a token for another account, or an account that was never locked all read as\n * \"account is reactivated\" — and the person then could not sign in.\n *\n * A refusal returns to the form with a reason, the shape `/auth/reset-password` already uses.\n */\n if (!succeed) {\n const message = t('text.invalid verification token')\n\n context.status = 404\n context.body = message\n\n if (accepts(header.accept, ['text/html', '*/*'])) {\n await context.render('auth-page', {\n pageElement: 'unlock-user',\n elementScript: '/auth/unlock-user.js',\n data: {\n token,\n message,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n }\n\n return\n }\n\n context.body = t('text.password reset succeed')\n\n clearAccessTokenCookie(context)\n\n if (accepts(header.accept, ['text/html', '*/*'])) {\n await context.render('auth-page', {\n pageElement: 'auth-result',\n elementScript: '/auth/result.js',\n data: {\n message: t('text.account is reactivated'),\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n }\n})\n"]}
|
|
@@ -32,7 +32,7 @@ var AccessRequestStatus;
|
|
|
32
32
|
* ── At most one pending per person and privilege ─────────────────────────
|
|
33
33
|
* Asking again bumps `count` and `lastRequestedAt` instead of adding a row. A partial unique index would say
|
|
34
34
|
* it and is not portable across the databases this framework supports, so it is held where the request is
|
|
35
|
-
* made
|
|
35
|
+
* made. The index below is for reading.
|
|
36
36
|
*/
|
|
37
37
|
let AccessRequest = class AccessRequest {
|
|
38
38
|
};
|
|
@@ -94,7 +94,7 @@ tslib_1.__decorate([
|
|
|
94
94
|
(0, typeorm_1.Column)({ nullable: true }),
|
|
95
95
|
(0, type_graphql_1.Field)({ nullable: true, description: 'When it was last asked.' }),
|
|
96
96
|
tslib_1.__metadata("design:type", Date
|
|
97
|
-
/*
|
|
97
|
+
/* An enum where the database has one, a string elsewhere. */
|
|
98
98
|
)
|
|
99
99
|
], AccessRequest.prototype, "lastRequestedAt", void 0);
|
|
100
100
|
tslib_1.__decorate([
|