@things-factory/auth-base 10.1.39 → 10.1.44
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/reset-password.d.ts +6 -0
- package/dist-server/controllers/reset-password.js +12 -13
- package/dist-server/controllers/reset-password.js.map +1 -1
- package/dist-server/controllers/unlock-user.d.ts +6 -0
- package/dist-server/controllers/unlock-user.js +12 -13
- package/dist-server/controllers/unlock-user.js.map +1 -1
- package/dist-server/controllers/utils/find-verification-token.d.ts +16 -0
- package/dist-server/controllers/utils/find-verification-token.js +40 -0
- package/dist-server/controllers/utils/find-verification-token.js.map +1 -0
- package/dist-server/controllers/utils/save-verification-token.d.ts +8 -1
- package/dist-server/controllers/utils/save-verification-token.js +10 -2
- package/dist-server/controllers/utils/save-verification-token.js.map +1 -1
- package/dist-server/controllers/verification.d.ts +6 -0
- package/dist-server/controllers/verification.js +13 -8
- package/dist-server/controllers/verification.js.map +1 -1
- package/dist-server/middlewares/jwt-authenticate-middleware.js +3 -1
- package/dist-server/middlewares/jwt-authenticate-middleware.js.map +1 -1
- package/dist-server/router/auth-checkin-router.js +2 -1
- package/dist-server/router/auth-checkin-router.js.map +1 -1
- package/dist-server/router/auth-public-process-router.js +31 -8
- package/dist-server/router/auth-public-process-router.js.map +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/service/role-template/index.d.ts +1 -1
- package/dist-server/service/verification-token/verification-token.d.ts +5 -0
- package/dist-server/service/verification-token/verification-token.js +6 -1
- package/dist-server/service/verification-token/verification-token.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/utils/same-origin-redirect.d.ts +4 -0
- package/dist-server/utils/same-origin-redirect.js +39 -0
- package/dist-server/utils/same-origin-redirect.js.map +1 -0
- package/dist-server/utils/verification-token-rule.d.ts +16 -0
- package/dist-server/utils/verification-token-rule.js +73 -0
- package/dist-server/utils/verification-token-rule.js.map +1 -0
- package/package.json +4 -4
- package/tests/invitation-route.test.ts +36 -17
- package/tests/lock-recovery.test.ts +3 -1
- package/tests/open-fields-framework.test.ts +212 -0
- package/tests/open-redirect-db.test.ts +143 -0
- package/tests/verification-token-db.test.ts +277 -0
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page the mail links to. The token rides after `#`, so the browser never sends it — not to our
|
|
3
|
+
* access log, not to a proxy's. The page reads it and posts it. `tests/invitation-route.test.ts`
|
|
4
|
+
* holds this against the router's table.
|
|
5
|
+
*/
|
|
6
|
+
export declare const RESET_PASSWORD_PATH = "/auth/reset-password";
|
|
1
7
|
export declare function sendPasswordResetEmail({ user, context }: {
|
|
2
8
|
user: any;
|
|
3
9
|
context: any;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RESET_PASSWORD_PATH = void 0;
|
|
3
4
|
exports.sendPasswordResetEmail = sendPasswordResetEmail;
|
|
4
5
|
exports.resetPassword = resetPassword;
|
|
5
|
-
const url_1 = require("url");
|
|
6
6
|
const email_base_1 = require("@things-factory/email-base");
|
|
7
7
|
const env_1 = require("@things-factory/env");
|
|
8
8
|
const shell_1 = require("@things-factory/shell");
|
|
@@ -12,15 +12,23 @@ const password_history_js_1 = require("../service/password-history/password-hist
|
|
|
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
14
|
const reset_password_email_js_1 = require("../templates/reset-password-email.js");
|
|
15
|
+
const build_auth_url_js_1 = require("../utils/build-auth-url.js");
|
|
16
|
+
const find_verification_token_js_1 = require("./utils/find-verification-token.js");
|
|
15
17
|
const make_verification_token_js_1 = require("./utils/make-verification-token.js");
|
|
16
18
|
const save_verification_token_js_1 = require("./utils/save-verification-token.js");
|
|
17
19
|
const HISTORY_SIZE = env_1.config.get('password', { history: 0 }).history;
|
|
20
|
+
/**
|
|
21
|
+
* The page the mail links to. The token rides after `#`, so the browser never sends it — not to our
|
|
22
|
+
* access log, not to a proxy's. The page reads it and posts it. `tests/invitation-route.test.ts`
|
|
23
|
+
* holds this against the router's table.
|
|
24
|
+
*/
|
|
25
|
+
exports.RESET_PASSWORD_PATH = '/auth/reset-password';
|
|
18
26
|
async function sendPasswordResetEmail({ user, context }) {
|
|
19
27
|
try {
|
|
20
28
|
var token = (0, make_verification_token_js_1.makeVerificationToken)();
|
|
21
29
|
var verifaction = await (0, save_verification_token_js_1.saveVerificationToken)(user.id, token, verification_token_js_1.VerificationTokenType.PASSWORD_RESET);
|
|
22
30
|
if (verifaction) {
|
|
23
|
-
var serviceUrl =
|
|
31
|
+
var serviceUrl = `${(0, build_auth_url_js_1.buildAuthURL)(exports.RESET_PASSWORD_PATH, context)}#${token}`;
|
|
24
32
|
await (0, email_base_1.sendEmail)({
|
|
25
33
|
receiver: user.email,
|
|
26
34
|
subject: 'Reset your password',
|
|
@@ -38,12 +46,7 @@ async function sendPasswordResetEmail({ user, context }) {
|
|
|
38
46
|
}
|
|
39
47
|
async function resetPassword(token, password, context) {
|
|
40
48
|
const { t } = context;
|
|
41
|
-
const verificationToken = await (0,
|
|
42
|
-
where: {
|
|
43
|
-
token,
|
|
44
|
-
type: verification_token_js_1.VerificationTokenType.PASSWORD_RESET
|
|
45
|
-
}
|
|
46
|
-
});
|
|
49
|
+
const verificationToken = await (0, find_verification_token_js_1.findLiveVerificationToken)(token, verification_token_js_1.VerificationTokenType.PASSWORD_RESET);
|
|
47
50
|
if (!verificationToken) {
|
|
48
51
|
throw new Error(t('text.invalid verification token'));
|
|
49
52
|
}
|
|
@@ -89,11 +92,7 @@ async function resetPassword(token, password, context) {
|
|
|
89
92
|
...user,
|
|
90
93
|
passwordUpdatedAt: new Date()
|
|
91
94
|
});
|
|
92
|
-
await (0,
|
|
93
|
-
userId,
|
|
94
|
-
token,
|
|
95
|
-
type: verification_token_js_1.VerificationTokenType.PASSWORD_RESET
|
|
96
|
-
});
|
|
95
|
+
await (0, find_verification_token_js_1.consumeVerificationToken)(verificationToken);
|
|
97
96
|
if (HISTORY_SIZE > 0) {
|
|
98
97
|
history = [
|
|
99
98
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reset-password.js","sourceRoot":"","sources":["../../server/controllers/reset-password.ts"],"names":[],"mappings":"
|
|
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 } 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\nexport async function resetPassword(token, password, context) {\n const { t } = context\n\n const verificationToken = await findLiveVerificationToken(token, VerificationTokenType.PASSWORD_RESET)\n\n if (!verificationToken) {\n throw new Error(t('text.invalid verification token'))\n }\n\n const { userId } = verificationToken\n if (!userId) {\n throw new Error(t('text.invalid verification token'))\n }\n\n var user = await getRepository(User).findOneBy({ id: userId })\n if (!user) {\n throw new Error(t('error.user not found'))\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 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"]}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page the mail links to. The token rides after `#`, so the browser never sends it — not to our
|
|
3
|
+
* access log, not to a proxy's. The page reads it and posts it. `tests/invitation-route.test.ts`
|
|
4
|
+
* holds this against the router's table.
|
|
5
|
+
*/
|
|
6
|
+
export declare const UNLOCK_USER_PATH = "/auth/unlock-user";
|
|
1
7
|
export declare function sendUnlockUserEmail({ user, context }: {
|
|
2
8
|
user: any;
|
|
3
9
|
context: any;
|
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UNLOCK_USER_PATH = void 0;
|
|
3
4
|
exports.sendUnlockUserEmail = sendUnlockUserEmail;
|
|
4
5
|
exports.unlockUser = unlockUser;
|
|
5
|
-
const url_1 = require("url");
|
|
6
6
|
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
10
|
const account_unlock_email_js_1 = require("../templates/account-unlock-email.js");
|
|
11
|
+
const build_auth_url_js_1 = require("../utils/build-auth-url.js");
|
|
12
|
+
const find_verification_token_js_1 = require("./utils/find-verification-token.js");
|
|
11
13
|
const make_verification_token_js_1 = require("./utils/make-verification-token.js");
|
|
12
14
|
const save_verification_token_js_1 = require("./utils/save-verification-token.js");
|
|
15
|
+
/**
|
|
16
|
+
* The page the mail links to. The token rides after `#`, so the browser never sends it — not to our
|
|
17
|
+
* access log, not to a proxy's. The page reads it and posts it. `tests/invitation-route.test.ts`
|
|
18
|
+
* holds this against the router's table.
|
|
19
|
+
*/
|
|
20
|
+
exports.UNLOCK_USER_PATH = '/auth/unlock-user';
|
|
13
21
|
async function sendUnlockUserEmail({ user, context }) {
|
|
14
22
|
try {
|
|
15
23
|
var token = (0, make_verification_token_js_1.makeVerificationToken)();
|
|
16
24
|
var verifaction = await (0, save_verification_token_js_1.saveVerificationToken)(user.id, token, verification_token_js_1.VerificationTokenType.UNLOCK);
|
|
17
25
|
if (verifaction) {
|
|
18
|
-
var serviceUrl =
|
|
26
|
+
var serviceUrl = `${(0, build_auth_url_js_1.buildAuthURL)(exports.UNLOCK_USER_PATH, context)}#${token}`;
|
|
19
27
|
await (0, email_base_1.sendEmail)({
|
|
20
28
|
receiver: user.email,
|
|
21
29
|
subject: 'Your account is locked',
|
|
@@ -48,12 +56,7 @@ async function unlockUser(token, password) {
|
|
|
48
56
|
if (!token || !password) {
|
|
49
57
|
return false;
|
|
50
58
|
}
|
|
51
|
-
const verification = await (0,
|
|
52
|
-
where: {
|
|
53
|
-
token,
|
|
54
|
-
type: verification_token_js_1.VerificationTokenType.UNLOCK
|
|
55
|
-
}
|
|
56
|
-
});
|
|
59
|
+
const verification = await (0, find_verification_token_js_1.findLiveVerificationToken)(token, verification_token_js_1.VerificationTokenType.UNLOCK);
|
|
57
60
|
/* Refuse a token that is gone, rather than destructuring null — a reused link showed the
|
|
58
61
|
screen a 500 instead of a reason. `resetPassword` refuses the same way. */
|
|
59
62
|
if (!verification?.userId) {
|
|
@@ -69,11 +72,7 @@ async function unlockUser(token, password) {
|
|
|
69
72
|
userInfo.password = user_js_1.User.encode(password, userInfo.salt);
|
|
70
73
|
userInfo.failCount = 0;
|
|
71
74
|
await (0, shell_1.getRepository)(user_js_1.User).save(userInfo);
|
|
72
|
-
await (0,
|
|
73
|
-
userId,
|
|
74
|
-
token,
|
|
75
|
-
type: verification_token_js_1.VerificationTokenType.UNLOCK
|
|
76
|
-
});
|
|
75
|
+
await (0, find_verification_token_js_1.consumeVerificationToken)(verification);
|
|
77
76
|
return true;
|
|
78
77
|
}
|
|
79
78
|
//# sourceMappingURL=unlock-user.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unlock-user.js","sourceRoot":"","sources":["../../server/controllers/unlock-user.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"unlock-user.js","sourceRoot":"","sources":["../../server/controllers/unlock-user.ts"],"names":[],"mappings":";;;AAkBA,kDAsBC;AAED,gCAuCC;AAjFD,2DAAsD;AACtD,iDAAqD;AAErD,qDAA0D;AAC1D,+FAA2F;AAC3F,kFAA6E;AAC7E,kEAAyD;AACzD,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,OAAO,EAAE,wBAAwB;gBACjC,OAAO,EAAE,IAAA,gDAAsB,EAAC;oBAC9B,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,QAAQ,EAAE,UAAU;iBACrB,CAAC;aACH,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 { getUnlockUserEmailForm } from '../templates/account-unlock-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\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 subject: 'Your account is locked',\n content: getUnlockUserEmailForm({\n username: user.username,\n name: user.name,\n resetUrl: serviceUrl\n })\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"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { VerificationToken, VerificationTokenType } from '../../service/verification-token/verification-token.js';
|
|
2
|
+
/**
|
|
3
|
+
* The stored token a mailed link carries, if it is still usable — the one lookup the reset, unlock
|
|
4
|
+
* and sign-up confirmation paths share.
|
|
5
|
+
*
|
|
6
|
+
* Found by hash and by type: a reset link does not confirm an address, and an activation link does
|
|
7
|
+
* not reset a password. A token past its `expiresAt`, or without one, answers `null` like a token
|
|
8
|
+
* that was never issued; the caller already refuses that.
|
|
9
|
+
*
|
|
10
|
+
* `token` is checked before the query. An absent condition is not a narrow condition — TypeORM drops
|
|
11
|
+
* `undefined` from `where` — and without the check a call with no token would match anyone's row of
|
|
12
|
+
* that type (the 2026-09-09 takeover, `unlock-user.ts`).
|
|
13
|
+
*/
|
|
14
|
+
export declare function findLiveVerificationToken(token: unknown, type: VerificationTokenType, now?: Date): Promise<VerificationToken | null>;
|
|
15
|
+
/** Uses up a token found by `findLiveVerificationToken`. */
|
|
16
|
+
export declare function consumeVerificationToken(found: VerificationToken): Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findLiveVerificationToken = findLiveVerificationToken;
|
|
4
|
+
exports.consumeVerificationToken = consumeVerificationToken;
|
|
5
|
+
const shell_1 = require("@things-factory/shell");
|
|
6
|
+
const verification_token_js_1 = require("../../service/verification-token/verification-token.js");
|
|
7
|
+
const verification_token_rule_js_1 = require("../../utils/verification-token-rule.js");
|
|
8
|
+
/**
|
|
9
|
+
* The stored token a mailed link carries, if it is still usable — the one lookup the reset, unlock
|
|
10
|
+
* and sign-up confirmation paths share.
|
|
11
|
+
*
|
|
12
|
+
* Found by hash and by type: a reset link does not confirm an address, and an activation link does
|
|
13
|
+
* not reset a password. A token past its `expiresAt`, or without one, answers `null` like a token
|
|
14
|
+
* that was never issued; the caller already refuses that.
|
|
15
|
+
*
|
|
16
|
+
* `token` is checked before the query. An absent condition is not a narrow condition — TypeORM drops
|
|
17
|
+
* `undefined` from `where` — and without the check a call with no token would match anyone's row of
|
|
18
|
+
* that type (the 2026-09-09 takeover, `unlock-user.ts`).
|
|
19
|
+
*/
|
|
20
|
+
async function findLiveVerificationToken(token, type, now = new Date()) {
|
|
21
|
+
if (typeof token !== 'string' || !token) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const found = await (0, shell_1.getRepository)(verification_token_js_1.VerificationToken).findOne({
|
|
25
|
+
where: {
|
|
26
|
+
token: (0, verification_token_rule_js_1.hashVerificationToken)(token),
|
|
27
|
+
type
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return found?.userId && (0, verification_token_rule_js_1.isVerificationTokenLive)(found, now) ? found : null;
|
|
31
|
+
}
|
|
32
|
+
/** Uses up a token found by `findLiveVerificationToken`. */
|
|
33
|
+
async function consumeVerificationToken(found) {
|
|
34
|
+
await (0, shell_1.getRepository)(verification_token_js_1.VerificationToken).delete({
|
|
35
|
+
userId: found.userId,
|
|
36
|
+
token: found.token,
|
|
37
|
+
type: found.type
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=find-verification-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-verification-token.js","sourceRoot":"","sources":["../../../server/controllers/utils/find-verification-token.ts"],"names":[],"mappings":";;AAiBA,8DAiBC;AAGD,4DAMC;AA3CD,iDAAqD;AAErD,kGAAiH;AACjH,uFAAuG;AAEvG;;;;;;;;;;;GAWG;AACI,KAAK,UAAU,yBAAyB,CAC7C,KAAc,EACd,IAA2B,EAC3B,MAAY,IAAI,IAAI,EAAE;IAEtB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC;QACxC,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,IAAA,qBAAa,EAAC,yCAAiB,CAAC,CAAC,OAAO,CAAC;QAC3D,KAAK,EAAE;YACL,KAAK,EAAE,IAAA,kDAAqB,EAAC,KAAK,CAAC;YACnC,IAAI;SACL;KACF,CAAC,CAAA;IAEF,OAAO,KAAK,EAAE,MAAM,IAAI,IAAA,oDAAuB,EAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;AAC5E,CAAC;AAED,4DAA4D;AACrD,KAAK,UAAU,wBAAwB,CAAC,KAAwB;IACrE,MAAM,IAAA,qBAAa,EAAC,yCAAiB,CAAC,CAAC,MAAM,CAAC;QAC5C,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { getRepository } from '@things-factory/shell'\n\nimport { VerificationToken, VerificationTokenType } from '../../service/verification-token/verification-token.js'\nimport { hashVerificationToken, isVerificationTokenLive } from '../../utils/verification-token-rule.js'\n\n/**\n * The stored token a mailed link carries, if it is still usable — the one lookup the reset, unlock\n * and sign-up confirmation paths share.\n *\n * Found by hash and by type: a reset link does not confirm an address, and an activation link does\n * not reset a password. A token past its `expiresAt`, or without one, answers `null` like a token\n * that was never issued; the caller already refuses that.\n *\n * `token` is checked before the query. An absent condition is not a narrow condition — TypeORM drops\n * `undefined` from `where` — and without the check a call with no token would match anyone's row of\n * that type (the 2026-09-09 takeover, `unlock-user.ts`).\n */\nexport async function findLiveVerificationToken(\n token: unknown,\n type: VerificationTokenType,\n now: Date = new Date()\n): Promise<VerificationToken | null> {\n if (typeof token !== 'string' || !token) {\n return null\n }\n\n const found = await getRepository(VerificationToken).findOne({\n where: {\n token: hashVerificationToken(token),\n type\n }\n })\n\n return found?.userId && isVerificationTokenLive(found, now) ? found : null\n}\n\n/** Uses up a token found by `findLiveVerificationToken`. */\nexport async function consumeVerificationToken(found: VerificationToken): Promise<void> {\n await getRepository(VerificationToken).delete({\n userId: found.userId,\n token: found.token,\n type: found.type\n })\n}\n"]}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { VerificationToken, VerificationTokenType } from '../../service/verification-token/verification-token.js';
|
|
2
|
+
/**
|
|
3
|
+
* Keeps a mailed token: its hash, and when it stops working. The token itself goes only into the mail.
|
|
4
|
+
*
|
|
5
|
+
* ⚠ The primary key is `userId`, so a person holds one token of any type at a time — saving a reset
|
|
6
|
+
* token replaces a pending activation or unlock token. `docs/design/auth-rebuild.md` §8-7.
|
|
7
|
+
*/
|
|
2
8
|
export declare function saveVerificationToken(id: any, token: any, type?: VerificationTokenType): Promise<{
|
|
3
9
|
userId: any;
|
|
4
|
-
token:
|
|
10
|
+
token: string;
|
|
5
11
|
type: VerificationTokenType;
|
|
12
|
+
expiresAt: Date;
|
|
6
13
|
} & VerificationToken>;
|
|
@@ -3,12 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.saveVerificationToken = saveVerificationToken;
|
|
4
4
|
const shell_1 = require("@things-factory/shell");
|
|
5
5
|
const verification_token_js_1 = require("../../service/verification-token/verification-token.js");
|
|
6
|
+
const verification_token_rule_js_1 = require("../../utils/verification-token-rule.js");
|
|
7
|
+
/**
|
|
8
|
+
* Keeps a mailed token: its hash, and when it stops working. The token itself goes only into the mail.
|
|
9
|
+
*
|
|
10
|
+
* ⚠ The primary key is `userId`, so a person holds one token of any type at a time — saving a reset
|
|
11
|
+
* token replaces a pending activation or unlock token. `docs/design/auth-rebuild.md` §8-7.
|
|
12
|
+
*/
|
|
6
13
|
async function saveVerificationToken(id, token, type = verification_token_js_1.VerificationTokenType.ACTIVATION) {
|
|
7
14
|
const verificationRepo = (0, shell_1.getRepository)(verification_token_js_1.VerificationToken);
|
|
8
15
|
return await verificationRepo.save({
|
|
9
16
|
userId: id,
|
|
10
|
-
token,
|
|
11
|
-
type
|
|
17
|
+
token: (0, verification_token_rule_js_1.hashVerificationToken)(token),
|
|
18
|
+
type,
|
|
19
|
+
expiresAt: (0, verification_token_rule_js_1.verificationTokenExpiresAt)(type)
|
|
12
20
|
});
|
|
13
21
|
}
|
|
14
22
|
//# sourceMappingURL=save-verification-token.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"save-verification-token.js","sourceRoot":"","sources":["../../../server/controllers/utils/save-verification-token.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"save-verification-token.js","sourceRoot":"","sources":["../../../server/controllers/utils/save-verification-token.ts"],"names":[],"mappings":";;AAWA,sDAQC;AAnBD,iDAAqD;AAErD,kGAAiH;AACjH,uFAA0G;AAE1G;;;;;GAKG;AACI,KAAK,UAAU,qBAAqB,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,GAAG,6CAAqB,CAAC,UAAU;IAC5F,MAAM,gBAAgB,GAAG,IAAA,qBAAa,EAAC,yCAAiB,CAAC,CAAA;IACzD,OAAO,MAAM,gBAAgB,CAAC,IAAI,CAAC;QACjC,MAAM,EAAE,EAAE;QACV,KAAK,EAAE,IAAA,kDAAqB,EAAC,KAAK,CAAC;QACnC,IAAI;QACJ,SAAS,EAAE,IAAA,uDAA0B,EAAC,IAAI,CAAC;KAC5C,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { getRepository } from '@things-factory/shell'\n\nimport { VerificationToken, VerificationTokenType } from '../../service/verification-token/verification-token.js'\nimport { hashVerificationToken, verificationTokenExpiresAt } from '../../utils/verification-token-rule.js'\n\n/**\n * Keeps a mailed token: its hash, and when it stops working. The token itself goes only into the mail.\n *\n * ⚠ The primary key is `userId`, so a person holds one token of any type at a time — saving a reset\n * token replaces a pending activation or unlock token. `docs/design/auth-rebuild.md` §8-7.\n */\nexport async function saveVerificationToken(id, token, type = VerificationTokenType.ACTIVATION) {\n const verificationRepo = getRepository(VerificationToken)\n return await verificationRepo.save({\n userId: id,\n token: hashVerificationToken(token),\n type,\n expiresAt: verificationTokenExpiresAt(type)\n })\n}\n"]}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page the mail links to. The token rides after `#`, so the browser never sends it — not to our
|
|
3
|
+
* access log, not to a proxy's. The page reads it and posts it. `tests/invitation-route.test.ts`
|
|
4
|
+
* holds this against the router's table.
|
|
5
|
+
*/
|
|
6
|
+
export declare const VERIFY_EMAIL_PATH = "/auth/verify";
|
|
1
7
|
export declare function sendVerificationEmail({ user, context }: {
|
|
2
8
|
user: any;
|
|
3
9
|
context: any;
|
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VERIFY_EMAIL_PATH = void 0;
|
|
3
4
|
exports.sendVerificationEmail = sendVerificationEmail;
|
|
4
5
|
exports.verify = verify;
|
|
5
6
|
exports.resendVerificationEmail = resendVerificationEmail;
|
|
6
|
-
const url_1 = require("url");
|
|
7
7
|
const email_base_1 = require("@things-factory/email-base");
|
|
8
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
12
|
const verification_email_js_1 = require("../templates/verification-email.js");
|
|
13
|
+
const build_auth_url_js_1 = require("../utils/build-auth-url.js");
|
|
14
|
+
const find_verification_token_js_1 = require("./utils/find-verification-token.js");
|
|
13
15
|
const make_verification_token_js_1 = require("./utils/make-verification-token.js");
|
|
14
16
|
const save_verification_token_js_1 = require("./utils/save-verification-token.js");
|
|
17
|
+
/**
|
|
18
|
+
* The page the mail links to. The token rides after `#`, so the browser never sends it — not to our
|
|
19
|
+
* access log, not to a proxy's. The page reads it and posts it. `tests/invitation-route.test.ts`
|
|
20
|
+
* holds this against the router's table.
|
|
21
|
+
*/
|
|
22
|
+
exports.VERIFY_EMAIL_PATH = '/auth/verify';
|
|
15
23
|
async function sendVerificationEmail({ user, context }) {
|
|
16
24
|
try {
|
|
17
25
|
var token = (0, make_verification_token_js_1.makeVerificationToken)();
|
|
18
26
|
var verifaction = await (0, save_verification_token_js_1.saveVerificationToken)(user.id, token);
|
|
19
27
|
if (verifaction) {
|
|
20
|
-
var serviceUrl =
|
|
28
|
+
var serviceUrl = `${(0, build_auth_url_js_1.buildAuthURL)(exports.VERIFY_EMAIL_PATH, context)}#${token}`;
|
|
21
29
|
await (0, email_base_1.sendEmail)({
|
|
22
30
|
receiver: user.email,
|
|
23
31
|
subject: 'Verify your email',
|
|
@@ -35,11 +43,8 @@ async function sendVerificationEmail({ user, context }) {
|
|
|
35
43
|
}
|
|
36
44
|
}
|
|
37
45
|
async function verify(token) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
token
|
|
41
|
-
}
|
|
42
|
-
});
|
|
46
|
+
/* Activation only. This read the token alone, so a reset or unlock link confirmed an address too. */
|
|
47
|
+
var verification = await (0, find_verification_token_js_1.findLiveVerificationToken)(token, verification_token_js_1.VerificationTokenType.ACTIVATION);
|
|
43
48
|
if (!verification) {
|
|
44
49
|
throw new auth_error_js_1.AuthError({
|
|
45
50
|
errorCode: auth_error_js_1.AuthError.ERROR_CODES.VERIFICATION_ERROR
|
|
@@ -59,7 +64,7 @@ async function verify(token) {
|
|
|
59
64
|
userInfo.status = user_js_1.UserStatus.ACTIVATED;
|
|
60
65
|
userInfo.failCount = 0;
|
|
61
66
|
await (0, shell_1.getRepository)(user_js_1.User).save(userInfo);
|
|
62
|
-
await (0,
|
|
67
|
+
await (0, find_verification_token_js_1.consumeVerificationToken)(verification);
|
|
63
68
|
}
|
|
64
69
|
async function resendVerificationEmail(email, context) {
|
|
65
70
|
var user = await (0, shell_1.getRepository)(user_js_1.User).findOne({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verification.js","sourceRoot":"","sources":["../../server/controllers/verification.ts"],"names":[],"mappings":"
|
|
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 { getVerificationEmailForm } from '../templates/verification-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\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 subject: 'Verify your email',\n content: getVerificationEmailForm({\n username: user.username,\n name: user.name,\n verifyUrl: serviceUrl\n })\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\nexport async function resendVerificationEmail(email, context) {\n var user = await getRepository(User).findOne({\n where: {\n email\n }\n })\n\n if (!user) return false\n if (user.status == UserStatus.ACTIVATED) return false\n\n return await sendVerificationEmail({\n user,\n context\n })\n}\n"]}
|
|
@@ -5,6 +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");
|
|
8
9
|
const make_verification_token_js_1 = require("../controllers/utils/make-verification-token.js");
|
|
9
10
|
const save_verification_token_js_1 = require("../controllers/utils/save-verification-token.js");
|
|
10
11
|
const user_js_1 = require("../service/user/user.js");
|
|
@@ -54,7 +55,8 @@ async function jwtAuthenticateMiddleware(context, next) {
|
|
|
54
55
|
const token = (0, make_verification_token_js_1.makeVerificationToken)();
|
|
55
56
|
await (0, save_verification_token_js_1.saveVerificationToken)(userEntity.id, token, verification_token_js_1.VerificationTokenType.PASSWORD_RESET);
|
|
56
57
|
(0, access_token_cookie_js_1.clearAccessTokenCookie)(context);
|
|
57
|
-
|
|
58
|
+
/* After `#`, like the mailed link — the browser keeps it and does not send it on. */
|
|
59
|
+
context.redirect(`${reset_password_js_1.RESET_PASSWORD_PATH}#${token}`);
|
|
58
60
|
}
|
|
59
61
|
catch (e) {
|
|
60
62
|
throw err;
|
|
@@ -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":"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","sourcesContent":["import passport from 'koa-passport'\nimport { ExtractJwt, Strategy as JWTstrategy } from 'passport-jwt'\n\nimport { config } from '@things-factory/env'\n\nimport { RESET_PASSWORD_PATH } from '../controllers/reset-password.js'\nimport { makeVerificationToken } from '../controllers/utils/make-verification-token.js'\nimport { saveVerificationToken } from '../controllers/utils/save-verification-token.js'\nimport { User, UserStatus } from '../service/user/user.js'\nimport { VerificationTokenType } from '../service/verification-token/verification-token.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 const userEntity = await User.checkAuth(decoded)\n\n if (userEntity.status === UserStatus.PWD_RESET_REQUIRED) {\n try {\n const token = makeVerificationToken()\n await saveVerificationToken(userEntity.id, token, VerificationTokenType.PASSWORD_RESET)\n clearAccessTokenCookie(context)\n /* After `#`, like the mailed link — the browser keeps it and does not send it on. */\n context.redirect(`${RESET_PASSWORD_PATH}#${token}`)\n } catch (e) {\n throw err\n }\n } else {\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 }\n })(context, next)\n}\n"]}
|
|
@@ -9,6 +9,7 @@ const login_history_js_1 = require("../service/login-history/login-history.js");
|
|
|
9
9
|
const accepts_js_1 = require("../utils/accepts.js");
|
|
10
10
|
const access_token_cookie_js_1 = require("../utils/access-token-cookie.js");
|
|
11
11
|
const get_user_domains_js_1 = require("../utils/get-user-domains.js");
|
|
12
|
+
const same_origin_redirect_js_1 = require("../utils/same-origin-redirect.js");
|
|
12
13
|
const domainTypes = env_1.config.get('domainTypes');
|
|
13
14
|
exports.authCheckinRouter = new koa_router_1.default();
|
|
14
15
|
exports.authCheckinRouter.get('/auth/checkin/:subdomain?', async (context, next) => {
|
|
@@ -35,7 +36,7 @@ exports.authCheckinRouter.get('/auth/checkin/:subdomain?', async (context, next)
|
|
|
35
36
|
}
|
|
36
37
|
else {
|
|
37
38
|
// When request expects html response
|
|
38
|
-
const
|
|
39
|
+
const redirectTo = (0, same_origin_redirect_js_1.sameOriginRedirectPath)(context.query.redirect_to, context);
|
|
39
40
|
try {
|
|
40
41
|
let message;
|
|
41
42
|
if (!subdomain) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-checkin-router.js","sourceRoot":"","sources":["../../server/router/auth-checkin-router.ts"],"names":[],"mappings":";;;;AAAA,oEAA+B;AAE/B,6CAA4C;AAC5C,iDAA+F;AAE/F,gFAAwE;AAExE,oDAA6C;AAC7C,4EAAwE;AACxE,sEAA6D;
|
|
1
|
+
{"version":3,"file":"auth-checkin-router.js","sourceRoot":"","sources":["../../server/router/auth-checkin-router.ts"],"names":[],"mappings":"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","sourcesContent":["import Router from 'koa-router'\n\nimport { config } from '@things-factory/env'\nimport { Domain, findSubdomainFromPath, getRedirectSubdomainPath } from '@things-factory/shell'\n\nimport { LoginHistory } from '../service/login-history/login-history.js'\nimport { User } from '../service/user/user.js'\nimport { accepts } from '../utils/accepts.js'\nimport { clearAccessTokenCookie } from '../utils/access-token-cookie.js'\nimport { getUserDomains } from '../utils/get-user-domains.js'\nimport { sameOriginRedirectPath } from '../utils/same-origin-redirect.js'\n\nconst domainTypes = config.get('domainTypes')\n\nexport const authCheckinRouter = new Router()\n\nauthCheckinRouter.get('/auth/checkin/:subdomain?', async (context, next) => {\n const { request, t } = context\n const header = request.header\n const { user } = context.state\n let { subdomain } = context.params\n\n let domains: Partial<Domain>[] = await getUserDomains(user)\n\n if (!accepts(header.accept, ['text/html', '*/*'])) {\n // When request expects non html response\n try {\n if (!subdomain) throw new Error(t('error.domain not specified', { subdomain })) // When params doesn't have subdomain\n const checkInDomain: Partial<Domain> | undefined = domains.find(d => d.subdomain === subdomain) // When no matched domain with subdomain\n if (!checkInDomain) throw new Error(t('error.domain not specified', { subdomain }))\n\n await checkIn(checkInDomain, null, context)\n context.body = true\n } catch (e) {\n clearAccessTokenCookie(context)\n throw e\n }\n } else {\n // When request expects html response\n const redirectTo = sameOriginRedirectPath(context.query.redirect_to, context)\n\n try {\n let message: string\n\n if (!subdomain) {\n /* try to find domain from redirectTo path */\n subdomain = findSubdomainFromPath(context, redirectTo)\n }\n\n let checkInDomain: Partial<Domain>\n if (subdomain) {\n checkInDomain = domains.find(d => d.subdomain == subdomain)\n if (!checkInDomain) message = t('error.domain not allowed', { subdomain })\n } else if (domains.length === 1) {\n checkInDomain = domains[0]\n }\n\n if (checkInDomain) {\n return await checkIn(checkInDomain, redirectTo, context)\n }\n\n await context.render('auth-page', {\n pageElement: 'auth-checkin',\n elementScript: '/auth/checkin.js',\n data: {\n user: {\n username: user.username,\n email: user.email,\n locale: user.locale,\n name: user.name,\n userType: user.userType\n },\n domains,\n domainTypes,\n redirectTo,\n message\n }\n })\n } catch (e) {\n clearAccessTokenCookie(context)\n context.redirect(\n `/auth/signin?username=${encodeURIComponent(user.username)}&redirect_to=${encodeURIComponent(redirectTo)}`\n )\n }\n }\n})\n\n// authCheckinRouter.get('/auth/domains', async context => {\n// const { user } = context.state\n// var domains = await getUserDomains(user)\n// if (domainTypes) {\n// domains = domains.filter(d => d.extType == domainTypes)\n// }\n\n// context.body = domains\n// })\n\nasync function checkIn(\n checkInDomain: Partial<Domain>,\n redirectTo: string | null,\n context: ResolverContext\n): Promise<void> {\n const { user }: { user: User } = context.state\n const remoteAddress = context.req.headers['x-forwarded-for']\n ? (context.req.headers['x-forwarded-for'] as string).split(',')[0].trim()\n : context.req.connection.remoteAddress\n\n await LoginHistory.stamp(checkInDomain, user, remoteAddress)\n\n if (redirectTo) {\n return context.redirect(getRedirectSubdomainPath(context, checkInDomain, redirectTo))\n }\n}\n"]}
|
|
@@ -71,13 +71,15 @@ exports.authPublicProcessRouter.get('/forgot-password', async (context, next) =>
|
|
|
71
71
|
}
|
|
72
72
|
});
|
|
73
73
|
});
|
|
74
|
+
/*
|
|
75
|
+
* The mailed link is `/auth/reset-password#<token>` (and the same shape for unlock and verify). The
|
|
76
|
+
* page reads the token from the fragment; this request never carries it, so nothing here reads one.
|
|
77
|
+
*/
|
|
74
78
|
exports.authPublicProcessRouter.get('/reset-password', async (context, next) => {
|
|
75
|
-
const { token } = context.request.query;
|
|
76
79
|
await context.render('auth-page', {
|
|
77
80
|
pageElement: 'reset-password',
|
|
78
81
|
elementScript: '/auth/reset-password.js',
|
|
79
82
|
data: {
|
|
80
|
-
token,
|
|
81
83
|
passwordRule,
|
|
82
84
|
disableUserSignupProcess,
|
|
83
85
|
disableUserFavoredLanguage,
|
|
@@ -86,12 +88,10 @@ exports.authPublicProcessRouter.get('/reset-password', async (context, next) =>
|
|
|
86
88
|
});
|
|
87
89
|
});
|
|
88
90
|
exports.authPublicProcessRouter.get('/unlock-user', async (context, next) => {
|
|
89
|
-
const { token } = context.request.query;
|
|
90
91
|
await context.render('auth-page', {
|
|
91
92
|
pageElement: 'unlock-user',
|
|
92
93
|
elementScript: '/auth/unlock-user.js',
|
|
93
94
|
data: {
|
|
94
|
-
token,
|
|
95
95
|
disableUserSignupProcess,
|
|
96
96
|
disableUserFavoredLanguage,
|
|
97
97
|
languages
|
|
@@ -111,11 +111,34 @@ exports.authPublicProcessRouter.get('/activate/:email', async (context, next) =>
|
|
|
111
111
|
}
|
|
112
112
|
});
|
|
113
113
|
});
|
|
114
|
-
|
|
114
|
+
/*
|
|
115
|
+
* Confirming an address is a POST from the page, not the GET of the link. The link used to be
|
|
116
|
+
* `/auth/verify/<token>` and opening it confirmed — so did any mail scanner that fetched it, and the
|
|
117
|
+
* token sat in every access log on the way.
|
|
118
|
+
*/
|
|
119
|
+
exports.authPublicProcessRouter.get('/verify', async (context, next) => {
|
|
120
|
+
await context.render('auth-page', {
|
|
121
|
+
pageElement: 'auth-verify',
|
|
122
|
+
elementScript: '/auth/verify.js',
|
|
123
|
+
data: {
|
|
124
|
+
disableUserSignupProcess,
|
|
125
|
+
disableUserFavoredLanguage,
|
|
126
|
+
languages
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
exports.authPublicProcessRouter.post('/verify', async (context, next) => {
|
|
115
131
|
const { header, t } = context;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
132
|
+
const { token } = context.request.body || {};
|
|
133
|
+
let message;
|
|
134
|
+
try {
|
|
135
|
+
await (0, verification_js_1.verify)(token);
|
|
136
|
+
message = t('text.user activated successfully');
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
context.status = 404;
|
|
140
|
+
message = t('text.invalid verification token');
|
|
141
|
+
}
|
|
119
142
|
context.body = message;
|
|
120
143
|
if ((0, accepts_js_1.accepts)(header.accept, ['text/html', '*/*'])) {
|
|
121
144
|
await context.render('auth-page', {
|