@things-factory/auth-base 10.1.206 → 10.1.212
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/passkey.d.ts +35 -0
- package/dist-server/controllers/passkey.js +182 -0
- package/dist-server/controllers/passkey.js.map +1 -0
- package/dist-server/controllers/signin.d.ts +24 -0
- package/dist-server/controllers/signin.js +63 -39
- package/dist-server/controllers/signin.js.map +1 -1
- package/dist-server/middlewares/index.d.ts +0 -1
- package/dist-server/middlewares/index.js +0 -1
- package/dist-server/middlewares/index.js.map +1 -1
- package/dist-server/router/auth-signin-router.js +2 -1
- package/dist-server/router/auth-signin-router.js.map +1 -1
- package/dist-server/router/webauthn-router.js +55 -93
- package/dist-server/router/webauthn-router.js.map +1 -1
- package/dist-server/service/audit/audit-kinds.js +3 -1
- package/dist-server/service/audit/audit-kinds.js.map +1 -1
- package/dist-server/service/index.js +2 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/open-fields.js +4 -1
- package/dist-server/service/open-fields.js.map +1 -1
- package/dist-server/service/web-auth-credential/index.d.ts +3 -0
- package/dist-server/service/web-auth-credential/index.js +4 -1
- package/dist-server/service/web-auth-credential/index.js.map +1 -1
- package/dist-server/service/web-auth-credential/web-auth-credential-mutation.d.ts +7 -0
- package/dist-server/service/web-auth-credential/web-auth-credential-mutation.js +67 -0
- package/dist-server/service/web-auth-credential/web-auth-credential-mutation.js.map +1 -0
- package/dist-server/service/web-auth-credential/web-auth-credential-query.d.ts +5 -0
- package/dist-server/service/web-auth-credential/web-auth-credential-query.js +29 -0
- package/dist-server/service/web-auth-credential/web-auth-credential-query.js.map +1 -0
- package/dist-server/service/web-auth-credential/web-auth-credential.d.ts +4 -0
- package/dist-server/service/web-auth-credential/web-auth-credential.js +24 -2
- package/dist-server/service/web-auth-credential/web-auth-credential.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/tests/audit-db.test.ts +4 -4
- package/tests/passkey-authenticator.ts +104 -0
- package/tests/passkey-db.test.ts +286 -0
- package/translations/ko.json +5 -5
- package/dist-server/middlewares/webauthn-middleware.d.ts +0 -1
- package/dist-server/middlewares/webauthn-middleware.js +0 -103
- package/dist-server/middlewares/webauthn-middleware.js.map +0 -1
|
@@ -3,117 +3,79 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.webAuthnGlobalPrivateRouter = exports.webAuthnGlobalPublicRouter = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const koa_router_1 = tslib_1.__importDefault(require("koa-router"));
|
|
6
|
-
const shell_1 = require("@things-factory/shell");
|
|
7
6
|
const env_1 = require("@things-factory/env");
|
|
8
|
-
const
|
|
9
|
-
const web_auth_credential_js_1 = require("../service/web-auth-credential/web-auth-credential.js");
|
|
7
|
+
const passkey_js_1 = require("../controllers/passkey.js");
|
|
10
8
|
const access_token_cookie_js_1 = require("../utils/access-token-cookie.js");
|
|
11
|
-
const
|
|
9
|
+
const same_origin_redirect_js_1 = require("../utils/same-origin-redirect.js");
|
|
12
10
|
exports.webAuthnGlobalPublicRouter = new koa_router_1.default();
|
|
13
11
|
exports.webAuthnGlobalPrivateRouter = new koa_router_1.default();
|
|
14
12
|
const { name: rpName } = env_1.appPackage;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
where: { user: { id: user.id } }
|
|
26
|
-
});
|
|
27
|
-
if (webAuthCredentials.length === 0) {
|
|
13
|
+
const disableUserFavoredLanguage = env_1.config.get('i18n/disableUserFavoredLanguage', false);
|
|
14
|
+
const languages = env_1.config.get('i18n/languages', false);
|
|
15
|
+
/*
|
|
16
|
+
* A refusal throws `AuthError`; `authenticate401Middleware` answers it with 401 and the refusal in
|
|
17
|
+
* the request's language as the body. The screens read that body as the message.
|
|
18
|
+
*/
|
|
19
|
+
// Confirming it is the signed-in person - only their own passkeys can answer
|
|
20
|
+
exports.webAuthnGlobalPrivateRouter.get('/auth/verify-webauthn/challenge', async (context) => {
|
|
21
|
+
const options = await (0, passkey_js_1.reverifyOptions)(context.state.user, (0, passkey_js_1.siteOf)(context));
|
|
22
|
+
if (!options) {
|
|
28
23
|
context.status = 400;
|
|
29
24
|
context.body = { error: 'No biometric credentials registered for this user' };
|
|
30
25
|
return;
|
|
31
26
|
}
|
|
32
|
-
|
|
33
|
-
rpID,
|
|
34
|
-
userVerification: 'preferred',
|
|
35
|
-
allowCredentials: webAuthCredentials.map(credential => ({
|
|
36
|
-
id: credential.credentialId,
|
|
37
|
-
type: 'public-key'
|
|
38
|
-
}))
|
|
39
|
-
});
|
|
40
|
-
context.session.challenge = options.challenge;
|
|
27
|
+
(0, passkey_js_1.rememberChallenge)(context, options.challenge);
|
|
41
28
|
context.body = options;
|
|
42
29
|
});
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const { user } = context.state;
|
|
48
|
-
const { request } = context;
|
|
49
|
-
const { body: reqBody } = request;
|
|
50
|
-
if (!user) {
|
|
51
|
-
context.status = 401;
|
|
52
|
-
context.body = { verified: false, message: 'User not authenticated' };
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
context.body = {
|
|
56
|
-
verified: true,
|
|
57
|
-
message: 'Biometric authentication successful'
|
|
58
|
-
};
|
|
59
|
-
await next();
|
|
30
|
+
exports.webAuthnGlobalPrivateRouter.post('/auth/verify-webauthn', async (context) => {
|
|
31
|
+
const challenge = (0, passkey_js_1.takeChallenge)(context);
|
|
32
|
+
await (0, passkey_js_1.reverifyWithPasskey)(context.state.user, context.request.body, challenge, (0, passkey_js_1.siteOf)(context));
|
|
33
|
+
context.body = { verified: true, message: 'Biometric authentication successful' };
|
|
60
34
|
});
|
|
61
|
-
//
|
|
62
|
-
exports.webAuthnGlobalPrivateRouter.get('/auth/register-webauthn/challenge', async (context
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
const webAuthCredentials = await (0, shell_1.getRepository)(web_auth_credential_js_1.WebAuthCredential).find({
|
|
66
|
-
where: {
|
|
67
|
-
user: { id: user.id }
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
const options = await (0, server_1.generateRegistrationOptions)({
|
|
71
|
-
rpName,
|
|
72
|
-
rpID,
|
|
73
|
-
userName: user.email,
|
|
74
|
-
userDisplayName: user.name,
|
|
75
|
-
// Don't prompt users for additional information about the authenticator
|
|
76
|
-
// (Recommended for smoother UX)
|
|
77
|
-
attestationType: 'none',
|
|
78
|
-
// Prevent users from re-registering existing authenticators
|
|
79
|
-
excludeCredentials: webAuthCredentials.map(credential => ({
|
|
80
|
-
id: credential.credentialId
|
|
81
|
-
// Optional
|
|
82
|
-
// transports: credential.transports
|
|
83
|
-
})),
|
|
84
|
-
authenticatorSelection: {
|
|
85
|
-
// Defaults
|
|
86
|
-
residentKey: 'preferred',
|
|
87
|
-
userVerification: 'preferred',
|
|
88
|
-
// Optional
|
|
89
|
-
authenticatorAttachment: 'platform'
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
context.session.challenge = options.challenge;
|
|
35
|
+
// Adding a passkey for the signed-in person
|
|
36
|
+
exports.webAuthnGlobalPrivateRouter.get('/auth/register-webauthn/challenge', async (context) => {
|
|
37
|
+
const options = await (0, passkey_js_1.registrationOptions)(context.state.user, rpName, (0, passkey_js_1.siteOf)(context));
|
|
38
|
+
(0, passkey_js_1.rememberChallenge)(context, options.challenge);
|
|
93
39
|
context.body = options;
|
|
94
40
|
});
|
|
95
|
-
|
|
96
|
-
exports.webAuthnGlobalPrivateRouter.post('/auth/verify-registration',
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
41
|
+
/* The body is `{ response, name }` - the browser's answer, and the label the screen proposes. */
|
|
42
|
+
exports.webAuthnGlobalPrivateRouter.post('/auth/verify-registration', async (context) => {
|
|
43
|
+
const challenge = (0, passkey_js_1.takeChallenge)(context);
|
|
44
|
+
const { response, name } = context.request.body || {};
|
|
45
|
+
await (0, passkey_js_1.registerPasskey)(context.state.user, response, name, challenge, (0, passkey_js_1.siteOf)(context));
|
|
46
|
+
context.body = { verified: true };
|
|
47
|
+
});
|
|
48
|
+
/*
|
|
49
|
+
* After a password sign-in on the screen, before check-in: an offer to add a passkey on this device
|
|
50
|
+
* (ADR-0156 decision 5). The page moves on by itself where it has nothing to offer.
|
|
51
|
+
*/
|
|
52
|
+
exports.webAuthnGlobalPrivateRouter.get('/auth/passkey-offer', async (context) => {
|
|
53
|
+
await context.render('auth-page', {
|
|
54
|
+
pageElement: 'auth-passkey-offer',
|
|
55
|
+
elementScript: '/auth/passkey-offer.js',
|
|
56
|
+
data: {
|
|
57
|
+
username: context.state.user.username,
|
|
58
|
+
redirectTo: (0, same_origin_redirect_js_1.sameOriginRedirectPath)(context.query.redirect_to, context),
|
|
59
|
+
disableUserFavoredLanguage,
|
|
60
|
+
languages
|
|
61
|
+
}
|
|
103
62
|
});
|
|
104
|
-
|
|
63
|
+
});
|
|
64
|
+
// Signing in
|
|
65
|
+
exports.webAuthnGlobalPublicRouter.get('/auth/signin-webauthn/challenge', async (context) => {
|
|
66
|
+
const options = await (0, passkey_js_1.signinOptions)((0, passkey_js_1.siteOf)(context));
|
|
67
|
+
(0, passkey_js_1.rememberChallenge)(context, options.challenge);
|
|
105
68
|
context.body = options;
|
|
106
69
|
});
|
|
107
|
-
|
|
108
|
-
exports.webAuthnGlobalPublicRouter.post('/auth/signin-webauthn',
|
|
109
|
-
const
|
|
110
|
-
const {
|
|
111
|
-
const {
|
|
112
|
-
const token = await
|
|
70
|
+
/* The body is `{ response, redirectTo }`. */
|
|
71
|
+
exports.webAuthnGlobalPublicRouter.post('/auth/signin-webauthn', async (context) => {
|
|
72
|
+
const challenge = (0, passkey_js_1.takeChallenge)(context);
|
|
73
|
+
const { response, redirectTo } = context.request.body || {};
|
|
74
|
+
const { domain } = context.state;
|
|
75
|
+
const { token } = await (0, passkey_js_1.signinWithPasskey)(response, challenge, (0, passkey_js_1.siteOf)(context), context);
|
|
113
76
|
(0, access_token_cookie_js_1.setAccessTokenCookie)(context, token);
|
|
114
|
-
|
|
115
|
-
|
|
77
|
+
/* A two-step exchange run by fetch in the browser cannot follow a 3xx, so the next address is data. */
|
|
78
|
+
const redirectURL = `/auth/checkin${domain ? '/' + domain.subdomain : ''}?redirect_to=${encodeURIComponent(redirectTo || '/')}`;
|
|
116
79
|
context.body = { redirectURL, verified: true };
|
|
117
|
-
await next();
|
|
118
80
|
});
|
|
119
81
|
//# sourceMappingURL=webauthn-router.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webauthn-router.js","sourceRoot":"","sources":["../../server/router/webauthn-router.ts"],"names":[],"mappings":";;;;AAAA,oEAA+B;AAC/B,
|
|
1
|
+
{"version":3,"file":"webauthn-router.js","sourceRoot":"","sources":["../../server/router/webauthn-router.ts"],"names":[],"mappings":"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","sourcesContent":["import Router from 'koa-router'\nimport { appPackage, config } from '@things-factory/env'\n\nimport {\n registerPasskey,\n registrationOptions,\n rememberChallenge,\n reverifyOptions,\n reverifyWithPasskey,\n signinOptions,\n signinWithPasskey,\n siteOf,\n takeChallenge\n} from '../controllers/passkey.js'\nimport { setAccessTokenCookie } from '../utils/access-token-cookie.js'\nimport { sameOriginRedirectPath } from '../utils/same-origin-redirect.js'\n\nexport const webAuthnGlobalPublicRouter = new Router()\nexport const webAuthnGlobalPrivateRouter = new Router()\n\nconst { name: rpName } = appPackage as any\n\nconst disableUserFavoredLanguage = config.get('i18n/disableUserFavoredLanguage', false)\nconst languages = config.get('i18n/languages', false)\n\n/*\n * A refusal throws `AuthError`; `authenticate401Middleware` answers it with 401 and the refusal in\n * the request's language as the body. The screens read that body as the message.\n */\n\n// Confirming it is the signed-in person - only their own passkeys can answer\nwebAuthnGlobalPrivateRouter.get('/auth/verify-webauthn/challenge', async context => {\n const options = await reverifyOptions(context.state.user, siteOf(context))\n\n if (!options) {\n context.status = 400\n context.body = { error: 'No biometric credentials registered for this user' }\n return\n }\n\n rememberChallenge(context, options.challenge)\n context.body = options\n})\n\nwebAuthnGlobalPrivateRouter.post('/auth/verify-webauthn', async context => {\n const challenge = takeChallenge(context)\n await reverifyWithPasskey(context.state.user, context.request.body, challenge, siteOf(context))\n\n context.body = { verified: true, message: 'Biometric authentication successful' }\n})\n\n// Adding a passkey for the signed-in person\nwebAuthnGlobalPrivateRouter.get('/auth/register-webauthn/challenge', async context => {\n const options = await registrationOptions(context.state.user, rpName, siteOf(context))\n\n rememberChallenge(context, options.challenge)\n context.body = options\n})\n\n/* The body is `{ response, name }` - the browser's answer, and the label the screen proposes. */\nwebAuthnGlobalPrivateRouter.post('/auth/verify-registration', async context => {\n const challenge = takeChallenge(context)\n const { response, name } = context.request.body || {}\n\n await registerPasskey(context.state.user, response, name, challenge, siteOf(context))\n\n context.body = { verified: true }\n})\n\n/*\n * After a password sign-in on the screen, before check-in: an offer to add a passkey on this device\n * (ADR-0156 decision 5). The page moves on by itself where it has nothing to offer.\n */\nwebAuthnGlobalPrivateRouter.get('/auth/passkey-offer', async context => {\n await context.render('auth-page', {\n pageElement: 'auth-passkey-offer',\n elementScript: '/auth/passkey-offer.js',\n data: {\n username: context.state.user.username,\n redirectTo: sameOriginRedirectPath(context.query.redirect_to, context),\n disableUserFavoredLanguage,\n languages\n }\n })\n})\n\n// Signing in\nwebAuthnGlobalPublicRouter.get('/auth/signin-webauthn/challenge', async context => {\n const options = await signinOptions(siteOf(context))\n\n rememberChallenge(context, options.challenge)\n context.body = options\n})\n\n/* The body is `{ response, redirectTo }`. */\nwebAuthnGlobalPublicRouter.post('/auth/signin-webauthn', async context => {\n const challenge = takeChallenge(context)\n const { response, redirectTo } = context.request.body || {}\n const { domain } = context.state\n\n const { token } = await signinWithPasskey(response, challenge, siteOf(context), context)\n setAccessTokenCookie(context, token)\n\n /* A two-step exchange run by fetch in the browser cannot follow a 3xx, so the next address is data. */\n const redirectURL = `/auth/checkin${domain ? '/' + domain.subdomain : ''}?redirect_to=${encodeURIComponent(redirectTo || '/')}`\n\n context.body = { redirectURL, verified: true }\n})\n"]}
|
|
@@ -49,13 +49,15 @@ function allowedDetail(kind, detail) {
|
|
|
49
49
|
registerAuditKind('door.check');
|
|
50
50
|
registerAuditKind('door.result');
|
|
51
51
|
/* Signing in and out (ADR-0141 §3.3). The reason of a failure is the error code column. */
|
|
52
|
-
|
|
52
|
+
/* `method` is password or passkey (ADR-0156 decision 4). */
|
|
53
|
+
registerAuditKind('auth.signin', ['method']);
|
|
53
54
|
registerAuditKind('auth.checkin');
|
|
54
55
|
registerAuditKind('auth.signout');
|
|
55
56
|
registerAuditKind('password.change');
|
|
56
57
|
registerAuditKind('password.reset-by-admin');
|
|
57
58
|
registerAuditKind('credential.issue');
|
|
58
59
|
registerAuditKind('webauthn.register');
|
|
60
|
+
registerAuditKind('webauthn.remove');
|
|
59
61
|
/* Who may do what */
|
|
60
62
|
registerAuditKind('role.grant');
|
|
61
63
|
registerAuditKind('role.revoke');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit-kinds.js","sourceRoot":"","sources":["../../../server/service/audit/audit-kinds.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AAMH,8CAMC;AAED,kCAEC;AAMD,sCAkBC;AApCD,MAAM,KAAK,GAAG,IAAI,GAAG,EAA6B,CAAA;AAElD,SAAgB,iBAAiB,CAAC,IAAY,EAAE,SAA4B,EAAE;IAC5E,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC7B,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,eAAe,IAAI,mDAAmD,CAAC,CAAA;IACzF,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;AACzB,CAAC;AAED,SAAgB,WAAW,CAAC,IAAY;IACtC,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AACxB,CAAC;AAED;;;GAGG;AACH,SAAgB,aAAa,CAC3B,IAAY,EACZ,MAAyC;IAEzC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,eAAe,IAAI,kEAAkE,CAAC,CAAA;IACxG,CAAC;IACD,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAE7B,MAAM,IAAI,GAAqC,EAAE,CAAA;IACjD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,eAAe,IAAI,4BAA4B,GAAG,eAAe,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,GAAG,CAAC,CAAA;QAChH,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IACpE,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;AACpD,CAAC;AAED,WAAW;AACX,iBAAiB,CAAC,YAAY,CAAC,CAAA;AAC/B,iBAAiB,CAAC,aAAa,CAAC,CAAA;AAEhC,2FAA2F;AAC3F,iBAAiB,CAAC,aAAa,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"audit-kinds.js","sourceRoot":"","sources":["../../../server/service/audit/audit-kinds.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The kinds of audit action, and the `detail` keys each may carry (ADR-0141 §3.4).\n *\n * The audit stores only fields it allows. The fixed columns of `AuditAction` hold no business\n * value; `detail` is the one open place, so it takes only the keys its kind registered here, and\n * a kind nobody registered is refused - a module that wants a new event says so here, at import,\n * and a typo does not become a new kind quietly.\n *\n * A key holds a security state or a count, never a business value: a user's status (`activated` →\n * `locked`) is the account's security state; a salary is not, and has no key to go in.\n */\n\nexport type AuditDetailValue = string | number | boolean | null\n\nconst KINDS = new Map<string, readonly string[]>()\n\nexport function registerAuditKind(kind: string, detail: readonly string[] = []) {\n const known = KINDS.get(kind)\n if (known && known.join() !== detail.join()) {\n throw new Error(`Audit kind \"${kind}\" is registered twice with different detail keys.`)\n }\n KINDS.set(kind, detail)\n}\n\nexport function isAuditKind(kind: string): boolean {\n return KINDS.has(kind)\n}\n\n/**\n * The detail as it may be stored: unknown kinds throw, keys the kind did not register throw, and\n * values are cut to 64 characters. Throwing is right - this is a program writing the wrong thing.\n */\nexport function allowedDetail(\n kind: string,\n detail?: Record<string, AuditDetailValue>\n): Record<string, AuditDetailValue> | undefined {\n const keys = KINDS.get(kind)\n if (!keys) {\n throw new Error(`Audit kind \"${kind}\" is not registered. Register it with registerAuditKind() first.`)\n }\n if (!detail) return undefined\n\n const kept: Record<string, AuditDetailValue> = {}\n for (const [key, value] of Object.entries(detail)) {\n if (!keys.includes(key)) {\n throw new Error(`Audit kind \"${kind}\" does not allow detail \"${key}\". Allowed: ${keys.join(', ') || 'none'}.`)\n }\n kept[key] = typeof value === 'string' ? value.slice(0, 64) : value\n }\n return Object.keys(kept).length ? kept : undefined\n}\n\n/* Doors */\nregisterAuditKind('door.check')\nregisterAuditKind('door.result')\n\n/* Signing in and out (ADR-0141 §3.3). The reason of a failure is the error code column. */\n/* `method` is password or passkey (ADR-0156 decision 4). */\nregisterAuditKind('auth.signin', ['method'])\nregisterAuditKind('auth.checkin')\nregisterAuditKind('auth.signout')\nregisterAuditKind('password.change')\nregisterAuditKind('password.reset-by-admin')\nregisterAuditKind('credential.issue')\nregisterAuditKind('webauthn.register')\nregisterAuditKind('webauthn.remove')\n\n/* Who may do what */\nregisterAuditKind('role.grant')\nregisterAuditKind('role.revoke')\nregisterAuditKind('role.privilege.add')\nregisterAuditKind('role.privilege.remove')\nregisterAuditKind('role.delete')\nregisterAuditKind('user.status', ['from', 'to'])\nregisterAuditKind('domain.owner.add')\nregisterAuditKind('domain.owner.remove')\nregisterAuditKind('invitation.create')\nregisterAuditKind('invitation.status', ['from', 'to'])\n"]}
|
|
@@ -124,7 +124,8 @@ exports.schema = {
|
|
|
124
124
|
...index_js_10.resolvers,
|
|
125
125
|
...index_js_19.resolvers,
|
|
126
126
|
...index_js_20.resolvers,
|
|
127
|
-
...index_js_1.resolvers
|
|
127
|
+
...index_js_1.resolvers,
|
|
128
|
+
...index_js_18.resolvers
|
|
128
129
|
],
|
|
129
130
|
directives: {
|
|
130
131
|
privilege: privilege_directive_js_1.privilegeDirectiveResolver
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,mCAAmC;AACnC,wDAAkH;AAClH,8DAGwC;AACxC,uDAA+G;AAC/G,qDAAwE;AACxE,mDAAqG;AACrG,qDAA2G;AAC3G,0DAAkF;AAClF,sDAA2G;AAC3G,oDAAuG;AACvG,gDAIyB;AACzB,6DAAsE;AACtE,kDAA+F;AAC/F,2DAAiF;AACjF,oDAAqG;AACrG,+EAA2G;AAC3G,qDAA6C;AAC7C,+CAAsF;AACtF,wDAA6E;AAC7E,+CAAsF;AACtF,6DAAqF;AACrF,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,mCAAmC;AACnC,wDAAkH;AAClH,8DAGwC;AACxC,uDAA+G;AAC/G,qDAAwE;AACxE,mDAAqG;AACrG,qDAA2G;AAC3G,0DAAkF;AAClF,sDAA2G;AAC3G,oDAAuG;AACvG,gDAIyB;AACzB,6DAAsE;AACtE,kDAA+F;AAC/F,2DAAiF;AACjF,oDAAqG;AACrG,+EAA2G;AAC3G,qDAA6C;AAC7C,+CAAsF;AACtF,wDAA6E;AAC7E,+CAAsF;AACtF,6DAAqF;AACrF,8DAA+H;AAC/H,sDAAwG;AACxG,uDAA4G;AAE5G,yBAAyB;AACzB,yFAA8D;AAC9D,2EAAgD;AAChD,uEAA4C;AAC5C,mEAAwC;AACxC,mEAAwC;AACxC,yDAA8B;AAC9B,wEAA6C;AAC7C,gHAAgH;AAChH,6EAAqF;AAA5E,0HAAA,gBAAgB,OAAA;AACzB,2FAA2F;AAC3F,6EAAuE;AAA9D,4HAAA,kBAAkB,OAAA;AAG3B,2EAAgD;AAChD,uEAA4C;AAC5C,iFAAsD;AACtD,yDAA8B;AAC9B,+DAAoC;AACpC,yEAA8C;AAC9C,qEAA0C;AAC1C,uEAA4C;AAC5C,iFAAsD;AACtD,qFAA0D;AAC1D,mEAAwC;AACxC,kEAAuC;AACvC,yDAA0D;AAAjD,mHAAA,iBAAiB,OAAA;AAC1B,+DAAiH;AAAxG,sHAAA,iBAAiB,OAAA;AAAE,oHAAA,eAAe,OAAA;AAAE,+GAAA,UAAU,OAAA;AACvD,iEAAmF;AAA1E,uHAAA,iBAAiB,OAAA;AAAE,yHAAA,mBAAmB,OAAA;AAC/C,uFAA4D;AAC5D,uEAA4C;AAC5C,yEAA8C;AAC9C,6EAAkD;AAElD,kBAAkB;AAClB,6EAAkD;AAClD,yEAA8C;AAC9C,6EAAkD;AAClD,uFAA4D;AAC5D,2EAAgD;AAChD,qEAA0C;AAC1C,yEAA8C;AAC9C,+DAAoC;AACpC,+DAAoC;AACpC,6EAAkD;AAClD,mFAAwD;AAE3C,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,mBAA0B;IAC7B,GAAG,mBAAoB;IACvB,GAAG,mBAAmB;IACtB,GAAG,mBAAiB;IACpB,GAAG,oBAAiB;IACpB,GAAG,oBAAY;IACf,GAAG,oBAAY;IACf,GAAG,oBAAe;IAClB,GAAG,mBAAmB;IACtB,GAAG,mBAAkB;IACrB,GAAG,oBAAuB;IAC1B,GAAG,oBAAyB;IAC5B,GAAG,oBAAa;IAChB,GAAG,oBAAyB;IAC5B,GAAG,oBAAkB;IACrB,GAAG,oBAAmB;IACtB,GAAG,mBAAqB;CACzB,CAAA;AAED,8EAA8E;AACjE,QAAA,WAAW,GAAG,CAAC,GAAG,uBAAgB,CAAC,CAAA;AAEnC,QAAA,MAAM,GAAG;IACpB,QAAQ,EAAE;QACR,0BAA0B,EAA1B,mDAA0B;QAC1B,4BAA4B,EAA5B,8CAA4B;KAC7B;IAED,6FAA6F;IAC7F,UAAU,EAAV,2BAAU;IAEV,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,oBAA2B;QAC9B,GAAG,oBAAqB;QACxB,GAAG,oBAAoB;QACvB,GAAG,oBAAkB;QACrB,GAAG,qBAAkB;QACrB,GAAG,qBAAa;QAChB,GAAG,qBAAqB;QACxB,GAAG,qBAAa;QAChB,GAAG,qBAAgB;QACnB,GAAG,oBAAmB;QACtB,GAAG,oBAAkB;QACrB,GAAG,oBAAkB;QACrB,GAAG,oBAAuB;QAC1B,GAAG,qBAAc;QACjB,GAAG,qBAAkB;QACrB,GAAG,qBAAoB;QACvB,GAAG,oBAAsB;QACzB,GAAG,qBAA0B;KAC9B;IACD,UAAU,EAAE;QACV,SAAS,EAAE,mDAA0B;KACtC;CACF,CAAA","sourcesContent":["/* IMPORT ENTITIES AND RESOLVERS */\nimport { entities as AccessRequestEntities, resolvers as AccessRequestResolvers } from './access-request/index.js'\nimport {\n entities as UsersAuthProvidersEntities,\n resolvers as UsersAuthProvidersResolvers\n} from './users-auth-providers/index.js'\nimport { entities as AuthProviderEntities, resolvers as AuthProviderResolvers } from './auth-provider/index.js'\nimport { resolvers as AppbindingResolver } from './app-binding/index.js'\nimport { entities as ApplianceEntities, resolvers as ApplianceResolvers } from './appliance/index.js'\nimport { entities as ApplicationEntities, resolvers as ApplicationResolvers } from './application/index.js'\nimport { resolvers as DomainGeneratorResolver } from './domain-generator/index.js'\nimport { entities as GrantedRoleEntities, resolvers as GrantedRoleResolver } from './granted-role/index.js'\nimport { entities as InvitationEntities, resolvers as InvitationResolver } from './invitation/index.js'\nimport {\n entities as AuditEntities,\n resolvers as AuditResolvers,\n subscribers as AuditSubscribers\n} from './audit/index.js'\nimport { auditTargetDirectiveTypeDefs } from './audit/audit-target.js'\nimport { entities as PartnerEntities, resolvers as PartnerResolvers } from './partner/index.js'\nimport { entities as PasswordHistoryEntities } from './password-history/index.js'\nimport { entities as PrivilegeEntities, resolvers as PrivilegeResolvers } from './privilege/index.js'\nimport { privilegeDirectiveResolver, privilegeDirectiveTypeDefs } from './privilege/privilege-directive.js'\nimport { openFields } from './open-fields.js'\nimport { entities as RoleEntities, resolvers as RoleResolvers } from './role/index.js'\nimport { resolvers as RoleTemplateResolvers } from './role-template/index.js'\nimport { entities as UserEntities, resolvers as UserResolvers } from './user/index.js'\nimport { entities as VerificationTokenEntities } from './verification-token/index.js'\nimport { entities as WebAuthCredentialEntities, resolvers as WebAuthCredentialResolvers } from './web-auth-credential/index.js'\nimport { entities as DomainLinkEntities, resolvers as DomainLinkResolver } from './domain-link/index.js'\nimport { entities as DomainOwnerEntities, resolvers as DomainOwnerResolvers } from './domain-owner/index.js'\n\n/* EXPORT ENTITY TYPES */\nexport * from './users-auth-providers/users-auth-providers.js'\nexport * from './auth-provider/auth-provider.js'\nexport * from './application/application.js'\nexport * from './appliance/appliance.js'\nexport * from './privilege/privilege.js'\nexport * from './role/role.js'\nexport * from './privilege/privilege-axis.js'\n/* What a field asks for, by name — for another entrance to the same door (operato-twin ADR-0072 decision 5). */\nexport { privilegeOfField, type FieldGate } from './privilege/privilege-directive.js'\n/* Other entrances report AUTH's refusal without copying its cause or localized wording. */\nexport { privilegeRejection } from './privilege/privilege-rejection.js'\n/* How a module declares the fields it leaves open — see `privilege/open-field-rule.ts`. */\nexport { type OpenFieldsDeclaration } from './privilege/open-field-rule.js'\nexport * from './role-template/role-template.js'\nexport * from './role-template/role-task.js'\nexport * from './role-template/role-template-types.js'\nexport * from './user/user.js'\nexport * from './partner/partner.js'\nexport * from './granted-role/granted-role.js'\nexport * from './invitation/invitation.js'\nexport * from './app-binding/app-binding.js'\nexport * from './password-history/password-history.js'\nexport * from './verification-token/verification-token.js'\nexport * from './audit/audit-request.js'\nexport * from './audit/audit-action.js'\nexport { registerAuditKind } from './audit/audit-kinds.js'\nexport { recordAuditAction, noteAuditAction, flushAudit, type AuditActionInput } from './audit/audit-recorder.js'\nexport { purgeExpiredAudit, startAuditRetention } from './audit/audit-retention.js'\nexport * from './web-auth-credential/web-auth-credential.js'\nexport * from './domain-link/domain-link.js'\nexport * from './domain-owner/domain-owner.js'\nexport * from './access-request/access-request.js'\n\n/* EXPORT TYPES */\nexport * from './app-binding/app-binding-types.js'\nexport * from './appliance/appliance-types.js'\nexport * from './application/application-types.js'\nexport * from './domain-generator/domain-generator-types.js'\nexport * from './invitation/invitation-types.js'\nexport * from './partner/partner-types.js'\nexport * from './privilege/privilege-types.js'\nexport * from './role/role-types.js'\nexport * from './user/user-types.js'\nexport * from './domain-link/domain-link-types.js'\nexport * from './access-request/access-request-types.js'\n\nexport const entities = [\n /* ENTITIES */\n ...UsersAuthProvidersEntities,\n ...AuthProviderEntities,\n ...ApplicationEntities,\n ...ApplianceEntities,\n ...PrivilegeEntities,\n ...RoleEntities,\n ...UserEntities,\n ...PartnerEntities,\n ...GrantedRoleEntities,\n ...InvitationEntities,\n ...PasswordHistoryEntities,\n ...VerificationTokenEntities,\n ...AuditEntities,\n ...WebAuthCredentialEntities,\n ...DomainLinkEntities,\n ...DomainOwnerEntities,\n ...AccessRequestEntities\n]\n\n/* The audit of who may do what, written where it is saved (ADR-0141 §3.3). */\nexport const subscribers = [...AuditSubscribers]\n\nexport const schema = {\n typeDefs: {\n privilegeDirectiveTypeDefs,\n auditTargetDirectiveTypeDefs\n },\n\n /* The fields that carry no @privilege, and why — `open-fields.ts`, read by the directive. */\n openFields,\n\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...UsersAuthProvidersResolvers,\n ...AuthProviderResolvers,\n ...ApplicationResolvers,\n ...ApplianceResolvers,\n ...PrivilegeResolvers,\n ...RoleResolvers,\n ...RoleTemplateResolvers,\n ...UserResolvers,\n ...PartnerResolvers,\n ...GrantedRoleResolver,\n ...InvitationResolver,\n ...AppbindingResolver,\n ...DomainGeneratorResolver,\n ...AuditResolvers,\n ...DomainLinkResolver,\n ...DomainOwnerResolvers,\n ...AccessRequestResolvers,\n ...WebAuthCredentialResolvers\n ],\n directives: {\n privilege: privilegeDirectiveResolver\n }\n}\n"]}
|
|
@@ -24,7 +24,10 @@ exports.openFields = {
|
|
|
24
24
|
'Query.accessRequestDeciderCount': 'how many people an access request from the caller would reach - a number, never who',
|
|
25
25
|
'Query.tasksGranting': 'the tasks a refused person may ask for - the code catalog of work only, nothing of the domain (ADR-0103 decision 9)',
|
|
26
26
|
'Mutation.requestAccess': 'asked by someone refused for want of a role - a gate here would need the privilege being asked for. The door asks only for the caller and judges again that a role would open it (ADR-0103)',
|
|
27
|
-
'Mutation.withdrawAccessRequest': "withdraws the caller's own pending request and nobody else's"
|
|
27
|
+
'Mutation.withdrawAccessRequest': "withdraws the caller's own pending request and nobody else's",
|
|
28
|
+
'Query.myWebAuthCredentials': "the caller's own passkeys (ADR-0156 decision 6)",
|
|
29
|
+
'Mutation.updateMyWebAuthCredential': "renames the caller's own passkey and nobody else's",
|
|
30
|
+
'Mutation.deleteMyWebAuthCredential': "removes the caller's own passkey and nobody else's - losing a device must not wait for an administrator"
|
|
28
31
|
}
|
|
29
32
|
};
|
|
30
33
|
//# sourceMappingURL=open-fields.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open-fields.js","sourceRoot":"","sources":["../../server/service/open-fields.ts"],"names":[],"mappings":";;;AAEA;;;;;;GAMG;AACU,QAAA,UAAU,GAA0B;IAC/C,QAAQ,EAAE;QACR,sBAAsB,EAAE,4DAA4D;QACpF,eAAe,EAAE,wBAAwB;QACzC,oBAAoB,EAAE,0EAA0E;QAChG,4BAA4B,EAAE,sCAAsC;QACpE,8BAA8B,EAAE,2CAA2C;QAC3E,wCAAwC,EAAE,iCAAiC;QAC3E,4BAA4B,EAAE,qDAAqD;QACnF,oBAAoB,EAClB,2JAA2J;QAC7J,yBAAyB,EAAE,qFAAqF;QAChH,wBAAwB,EAAE,kCAAkC;QAC5D,4BAA4B,EAAE,oFAAoF;QAClH,iCAAiC,EAAE,qFAAqF;QACxH,qBAAqB,EAAE,qHAAqH;QAC5I,wBAAwB,EACtB,6LAA6L;QAC/L,gCAAgC,EAAE,8DAA8D;
|
|
1
|
+
{"version":3,"file":"open-fields.js","sourceRoot":"","sources":["../../server/service/open-fields.ts"],"names":[],"mappings":";;;AAEA;;;;;;GAMG;AACU,QAAA,UAAU,GAA0B;IAC/C,QAAQ,EAAE;QACR,sBAAsB,EAAE,4DAA4D;QACpF,eAAe,EAAE,wBAAwB;QACzC,oBAAoB,EAAE,0EAA0E;QAChG,4BAA4B,EAAE,sCAAsC;QACpE,8BAA8B,EAAE,2CAA2C;QAC3E,wCAAwC,EAAE,iCAAiC;QAC3E,4BAA4B,EAAE,qDAAqD;QACnF,oBAAoB,EAClB,2JAA2J;QAC7J,yBAAyB,EAAE,qFAAqF;QAChH,wBAAwB,EAAE,kCAAkC;QAC5D,4BAA4B,EAAE,oFAAoF;QAClH,iCAAiC,EAAE,qFAAqF;QACxH,qBAAqB,EAAE,qHAAqH;QAC5I,wBAAwB,EACtB,6LAA6L;QAC/L,gCAAgC,EAAE,8DAA8D;QAChG,4BAA4B,EAAE,iDAAiD;QAC/E,oCAAoC,EAAE,oDAAoD;QAC1F,oCAAoC,EAAE,yGAAyG;KAChJ;CACF,CAAA","sourcesContent":["import type { OpenFieldsDeclaration } from './privilege/open-field-rule.js'\n\n/**\n * auth-base's root fields that declare no `@privilege` — every one of them, and why.\n *\n * There is no `notYetDeclared` here. The twelve that were open without a reason were closed on\n * 2026-09-20 (`c02ee9bdc1`); what is left answers about the caller, or about the installation\n * rather than anybody's data. An addition to this list needs its reason written first.\n */\nexport const openFields: OpenFieldsDeclaration = {\n byDesign: {\n 'Query.myAuditActions': \"the caller's own actions - the actor is the session's user\",\n 'Query.myRoles': \"the caller's own roles\",\n 'Query.hasPrivilege': 'whether the caller has one — a gate here would need the answer to itself',\n 'Query.domainsWithPrivilege': 'the domains where the caller has one',\n 'Query.checkUserBelongsDomain': 'whether the caller belongs to this domain',\n 'Query.checkResettablePasswordToDefault': \"about the caller's own password\",\n 'Query.checkDefaultPassword': 'whether the caller is still on the default password',\n 'Query.passwordRule':\n \"the installation's password policy. The change-password screen needs it before the caller has any privilege — and after a forced reset they may have none\",\n 'Query.authProviderTypes': \"which provider types this build supports. A catalogue, not a tenant's configuration\",\n 'Query.myAccessRequests': \"the caller's own access requests\",\n 'Query.mayChangeParentRoles': \"whether the caller may give the parent domain's roles here - about the caller only\",\n 'Query.accessRequestDeciderCount': 'how many people an access request from the caller would reach - a number, never who',\n 'Query.tasksGranting': 'the tasks a refused person may ask for - the code catalog of work only, nothing of the domain (ADR-0103 decision 9)',\n 'Mutation.requestAccess':\n 'asked by someone refused for want of a role - a gate here would need the privilege being asked for. The door asks only for the caller and judges again that a role would open it (ADR-0103)',\n 'Mutation.withdrawAccessRequest': \"withdraws the caller's own pending request and nobody else's\",\n 'Query.myWebAuthCredentials': \"the caller's own passkeys (ADR-0156 decision 6)\",\n 'Mutation.updateMyWebAuthCredential': \"renames the caller's own passkey and nobody else's\",\n 'Mutation.deleteMyWebAuthCredential': \"removes the caller's own passkey and nobody else's - losing a device must not wait for an administrator\"\n }\n}\n"]}
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
import { WebAuthCredential } from './web-auth-credential.js';
|
|
2
|
+
import { WebAuthCredentialMutation } from './web-auth-credential-mutation.js';
|
|
3
|
+
import { WebAuthCredentialQuery } from './web-auth-credential-query.js';
|
|
2
4
|
export declare const entities: (typeof WebAuthCredential)[];
|
|
5
|
+
export declare const resolvers: (typeof WebAuthCredentialMutation | typeof WebAuthCredentialQuery)[];
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.entities = void 0;
|
|
3
|
+
exports.resolvers = exports.entities = void 0;
|
|
4
4
|
const web_auth_credential_js_1 = require("./web-auth-credential.js");
|
|
5
|
+
const web_auth_credential_mutation_js_1 = require("./web-auth-credential-mutation.js");
|
|
6
|
+
const web_auth_credential_query_js_1 = require("./web-auth-credential-query.js");
|
|
5
7
|
exports.entities = [web_auth_credential_js_1.WebAuthCredential];
|
|
8
|
+
exports.resolvers = [web_auth_credential_query_js_1.WebAuthCredentialQuery, web_auth_credential_mutation_js_1.WebAuthCredentialMutation];
|
|
6
9
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/web-auth-credential/index.ts"],"names":[],"mappings":";;;AAAA,qEAA4D;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/web-auth-credential/index.ts"],"names":[],"mappings":";;;AAAA,qEAA4D;AAC5D,uFAA6E;AAC7E,iFAAuE;AAE1D,QAAA,QAAQ,GAAG,CAAC,0CAAiB,CAAC,CAAA;AAC9B,QAAA,SAAS,GAAG,CAAC,qDAAsB,EAAE,2DAAyB,CAAC,CAAA","sourcesContent":["import { WebAuthCredential } from './web-auth-credential.js'\nimport { WebAuthCredentialMutation } from './web-auth-credential-mutation.js'\nimport { WebAuthCredentialQuery } from './web-auth-credential-query.js'\n\nexport const entities = [WebAuthCredential]\nexport const resolvers = [WebAuthCredentialQuery, WebAuthCredentialMutation]\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { WebAuthCredential } from './web-auth-credential.js';
|
|
2
|
+
export declare class WebAuthCredentialMutation {
|
|
3
|
+
/** Open by design: renames only the caller's own passkey. */
|
|
4
|
+
updateMyWebAuthCredential(id: string, name: string, context: ResolverContext): Promise<WebAuthCredential>;
|
|
5
|
+
/** Open by design: removes only the caller's own passkey. It signs nobody in afterwards. */
|
|
6
|
+
deleteMyWebAuthCredential(id: string, context: ResolverContext): Promise<boolean>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebAuthCredentialMutation = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const type_graphql_1 = require("type-graphql");
|
|
6
|
+
const shell_1 = require("@things-factory/shell");
|
|
7
|
+
const audit_recorder_js_1 = require("../audit/audit-recorder.js");
|
|
8
|
+
const web_auth_credential_js_1 = require("./web-auth-credential.js");
|
|
9
|
+
/**
|
|
10
|
+
* The caller's own passkeys (ADR-0156 decision 6). Another person's id is answered as one that does
|
|
11
|
+
* not exist - the answer does not tell whether it is somebody else's.
|
|
12
|
+
*/
|
|
13
|
+
async function ownPasskey(id, context) {
|
|
14
|
+
const { user, tx } = context.state;
|
|
15
|
+
const credential = await (0, shell_1.getRepository)(web_auth_credential_js_1.WebAuthCredential, tx).findOne({ where: { id, user: { id: user.id } } });
|
|
16
|
+
if (!credential) {
|
|
17
|
+
throw new shell_1.Refusal('passkey-not-found', { id }, 'There is no passkey of yours with this id.');
|
|
18
|
+
}
|
|
19
|
+
return credential;
|
|
20
|
+
}
|
|
21
|
+
let WebAuthCredentialMutation = class WebAuthCredentialMutation {
|
|
22
|
+
/** Open by design: renames only the caller's own passkey. */
|
|
23
|
+
async updateMyWebAuthCredential(id, name, context) {
|
|
24
|
+
const { user, tx } = context.state;
|
|
25
|
+
const credential = await ownPasskey(id, context);
|
|
26
|
+
return await (0, shell_1.getRepository)(web_auth_credential_js_1.WebAuthCredential, tx).save({
|
|
27
|
+
...credential,
|
|
28
|
+
name: name.trim().slice(0, 64) || null,
|
|
29
|
+
updater: user
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Open by design: removes only the caller's own passkey. It signs nobody in afterwards. */
|
|
33
|
+
async deleteMyWebAuthCredential(id, context) {
|
|
34
|
+
const { user, tx } = context.state;
|
|
35
|
+
const credential = await ownPasskey(id, context);
|
|
36
|
+
const repository = (0, shell_1.getRepository)(web_auth_credential_js_1.WebAuthCredential, tx);
|
|
37
|
+
await repository.update({ id: credential.id }, { updater: user });
|
|
38
|
+
await repository.softDelete({ id: credential.id });
|
|
39
|
+
await (0, audit_recorder_js_1.recordAuditAction)({ kind: 'webauthn.remove', targetKind: 'User', targetIds: [user.id], targetRole: 'handled' }, { manager: tx, actorId: user.id });
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
exports.WebAuthCredentialMutation = WebAuthCredentialMutation;
|
|
44
|
+
tslib_1.__decorate([
|
|
45
|
+
(0, type_graphql_1.Directive)('@transaction'),
|
|
46
|
+
(0, type_graphql_1.Mutation)(returns => web_auth_credential_js_1.WebAuthCredential, { description: "To rename one of the caller's own passkeys." }),
|
|
47
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
|
|
48
|
+
tslib_1.__param(1, (0, type_graphql_1.Arg)('name')),
|
|
49
|
+
tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
|
|
50
|
+
tslib_1.__metadata("design:type", Function),
|
|
51
|
+
tslib_1.__metadata("design:paramtypes", [String, String, Object]),
|
|
52
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
53
|
+
], WebAuthCredentialMutation.prototype, "updateMyWebAuthCredential", null);
|
|
54
|
+
tslib_1.__decorate([
|
|
55
|
+
(0, type_graphql_1.Directive)('@transaction'),
|
|
56
|
+
(0, type_graphql_1.Mutation)(returns => Boolean, { description: "To remove one of the caller's own passkeys. It can no longer sign in." }),
|
|
57
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
|
|
58
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
59
|
+
tslib_1.__metadata("design:type", Function),
|
|
60
|
+
tslib_1.__metadata("design:paramtypes", [String, Object]),
|
|
61
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
62
|
+
], WebAuthCredentialMutation.prototype, "deleteMyWebAuthCredential", null);
|
|
63
|
+
exports.WebAuthCredentialMutation = WebAuthCredentialMutation = tslib_1.__decorate([
|
|
64
|
+
(0, type_graphql_1.Resolver)(web_auth_credential_js_1.WebAuthCredential),
|
|
65
|
+
(0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals)
|
|
66
|
+
], WebAuthCredentialMutation);
|
|
67
|
+
//# sourceMappingURL=web-auth-credential-mutation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-auth-credential-mutation.js","sourceRoot":"","sources":["../../../server/service/web-auth-credential/web-auth-credential-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Mutation, Resolver, UseMiddleware } from 'type-graphql'\n\nimport { getRepository, Refusal, SpeakRefusals } from '@things-factory/shell'\n\nimport { recordAuditAction } from '../audit/audit-recorder.js'\nimport { WebAuthCredential } from './web-auth-credential.js'\n\n/**\n * The caller's own passkeys (ADR-0156 decision 6). Another person's id is answered as one that does\n * not exist - the answer does not tell whether it is somebody else's.\n */\nasync function ownPasskey(id: string, context: ResolverContext): Promise<WebAuthCredential> {\n const { user, tx } = context.state\n\n const credential = await getRepository(WebAuthCredential, tx).findOne({ where: { id, user: { id: user.id } } })\n if (!credential) {\n throw new Refusal('passkey-not-found', { id }, 'There is no passkey of yours with this id.')\n }\n\n return credential\n}\n\n@Resolver(WebAuthCredential)\n@UseMiddleware(SpeakRefusals)\nexport class WebAuthCredentialMutation {\n /** Open by design: renames only the caller's own passkey. */\n @Directive('@transaction')\n @Mutation(returns => WebAuthCredential, { description: \"To rename one of the caller's own passkeys.\" })\n async updateMyWebAuthCredential(\n @Arg('id') id: string,\n @Arg('name') name: string,\n @Ctx() context: ResolverContext\n ): Promise<WebAuthCredential> {\n const { user, tx } = context.state\n const credential = await ownPasskey(id, context)\n\n return await getRepository(WebAuthCredential, tx).save({\n ...credential,\n name: name.trim().slice(0, 64) || null,\n updater: user\n })\n }\n\n /** Open by design: removes only the caller's own passkey. It signs nobody in afterwards. */\n @Directive('@transaction')\n @Mutation(returns => Boolean, { description: \"To remove one of the caller's own passkeys. It can no longer sign in.\" })\n async deleteMyWebAuthCredential(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {\n const { user, tx } = context.state\n const credential = await ownPasskey(id, context)\n const repository = getRepository(WebAuthCredential, tx)\n\n await repository.update({ id: credential.id }, { updater: user })\n await repository.softDelete({ id: credential.id })\n await recordAuditAction(\n { kind: 'webauthn.remove', targetKind: 'User', targetIds: [user.id], targetRole: 'handled' },\n { manager: tx, actorId: user.id }\n )\n\n return true\n }\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { WebAuthCredential } from './web-auth-credential.js';
|
|
2
|
+
export declare class WebAuthCredentialQuery {
|
|
3
|
+
/** Open by design: the caller's own passkeys and nobody else's (ADR-0156 decision 6). */
|
|
4
|
+
myWebAuthCredentials(context: ResolverContext): Promise<WebAuthCredential[]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebAuthCredentialQuery = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const type_graphql_1 = require("type-graphql");
|
|
6
|
+
const shell_1 = require("@things-factory/shell");
|
|
7
|
+
const web_auth_credential_js_1 = require("./web-auth-credential.js");
|
|
8
|
+
let WebAuthCredentialQuery = class WebAuthCredentialQuery {
|
|
9
|
+
/** Open by design: the caller's own passkeys and nobody else's (ADR-0156 decision 6). */
|
|
10
|
+
async myWebAuthCredentials(context) {
|
|
11
|
+
const { user } = context.state;
|
|
12
|
+
return await (0, shell_1.getRepository)(web_auth_credential_js_1.WebAuthCredential).find({
|
|
13
|
+
where: { user: { id: user.id } },
|
|
14
|
+
order: { createdAt: 'DESC' }
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
exports.WebAuthCredentialQuery = WebAuthCredentialQuery;
|
|
19
|
+
tslib_1.__decorate([
|
|
20
|
+
(0, type_graphql_1.Query)(returns => [web_auth_credential_js_1.WebAuthCredential], { description: "To list the caller's own passkeys, newest first." }),
|
|
21
|
+
tslib_1.__param(0, (0, type_graphql_1.Ctx)()),
|
|
22
|
+
tslib_1.__metadata("design:type", Function),
|
|
23
|
+
tslib_1.__metadata("design:paramtypes", [Object]),
|
|
24
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
25
|
+
], WebAuthCredentialQuery.prototype, "myWebAuthCredentials", null);
|
|
26
|
+
exports.WebAuthCredentialQuery = WebAuthCredentialQuery = tslib_1.__decorate([
|
|
27
|
+
(0, type_graphql_1.Resolver)(web_auth_credential_js_1.WebAuthCredential)
|
|
28
|
+
], WebAuthCredentialQuery);
|
|
29
|
+
//# sourceMappingURL=web-auth-credential-query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-auth-credential-query.js","sourceRoot":"","sources":["../../../server/service/web-auth-credential/web-auth-credential-query.ts"],"names":[],"mappings":";;;;AAAA,+CAAmD;AAEnD,iDAAqD;AAErD,qEAA4D;AAGrD,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;IACjC,yFAAyF;IAEnF,AAAN,KAAK,CAAC,oBAAoB,CAAQ,OAAwB;QACxD,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAE9B,OAAO,MAAM,IAAA,qBAAa,EAAC,0CAAiB,CAAC,CAAC,IAAI,CAAC;YACjD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE;YAChC,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;SAC7B,CAAC,CAAA;IACJ,CAAC;CACF,CAAA;AAXY,wDAAsB;AAG3B;IADL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,0CAAiB,CAAC,EAAE,EAAE,WAAW,EAAE,kDAAkD,EAAE,CAAC;IAC/E,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;kEAOhC;iCAVU,sBAAsB;IADlC,IAAA,uBAAQ,EAAC,0CAAiB,CAAC;GACf,sBAAsB,CAWlC","sourcesContent":["import { Ctx, Query, Resolver } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { WebAuthCredential } from './web-auth-credential.js'\n\n@Resolver(WebAuthCredential)\nexport class WebAuthCredentialQuery {\n /** Open by design: the caller's own passkeys and nobody else's (ADR-0156 decision 6). */\n @Query(returns => [WebAuthCredential], { description: \"To list the caller's own passkeys, newest first.\" })\n async myWebAuthCredentials(@Ctx() context: ResolverContext): Promise<WebAuthCredential[]> {\n const { user } = context.state\n\n return await getRepository(WebAuthCredential).find({\n where: { user: { id: user.id } },\n order: { createdAt: 'DESC' }\n })\n }\n}\n"]}
|
|
@@ -6,8 +6,12 @@ export declare class WebAuthCredential {
|
|
|
6
6
|
credentialId: string;
|
|
7
7
|
publicKey: string;
|
|
8
8
|
counter: number;
|
|
9
|
+
name?: string;
|
|
10
|
+
transports?: string[];
|
|
11
|
+
lastUsedAt?: Date;
|
|
9
12
|
createdAt?: Date;
|
|
10
13
|
updatedAt?: Date;
|
|
14
|
+
deletedAt?: Date;
|
|
11
15
|
creator?: User;
|
|
12
16
|
creatorId?: string;
|
|
13
17
|
updater?: User;
|
|
@@ -37,6 +37,21 @@ tslib_1.__decorate([
|
|
|
37
37
|
(0, type_graphql_1.Field)({ nullable: true, description: 'The signature counter, used to prevent replay attacks.' }),
|
|
38
38
|
tslib_1.__metadata("design:type", Number)
|
|
39
39
|
], WebAuthCredential.prototype, "counter", void 0);
|
|
40
|
+
tslib_1.__decorate([
|
|
41
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
42
|
+
(0, type_graphql_1.Field)({ nullable: true, description: "The owner's label for this passkey, such as the device it was made on." }),
|
|
43
|
+
tslib_1.__metadata("design:type", String)
|
|
44
|
+
], WebAuthCredential.prototype, "name", void 0);
|
|
45
|
+
tslib_1.__decorate([
|
|
46
|
+
(0, typeorm_1.Column)('simple-array', { nullable: true }),
|
|
47
|
+
(0, type_graphql_1.Field)(type => [String], { nullable: true, description: 'How the authenticator can be reached: usb, nfc, ble, hybrid, internal.' }),
|
|
48
|
+
tslib_1.__metadata("design:type", Array)
|
|
49
|
+
], WebAuthCredential.prototype, "transports", void 0);
|
|
50
|
+
tslib_1.__decorate([
|
|
51
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
52
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'When this passkey was last used to sign in.' }),
|
|
53
|
+
tslib_1.__metadata("design:type", Date)
|
|
54
|
+
], WebAuthCredential.prototype, "lastUsedAt", void 0);
|
|
40
55
|
tslib_1.__decorate([
|
|
41
56
|
(0, typeorm_1.CreateDateColumn)(),
|
|
42
57
|
(0, type_graphql_1.Field)({ nullable: true, description: 'The timestamp when the credential was created.' }),
|
|
@@ -45,8 +60,15 @@ tslib_1.__decorate([
|
|
|
45
60
|
tslib_1.__decorate([
|
|
46
61
|
(0, typeorm_1.UpdateDateColumn)(),
|
|
47
62
|
(0, type_graphql_1.Field)({ nullable: true, description: 'The timestamp when the credential was last updated.' }),
|
|
48
|
-
tslib_1.__metadata("design:type", Date
|
|
63
|
+
tslib_1.__metadata("design:type", Date
|
|
64
|
+
/* A removed passkey signs nobody in - every find skips it. */
|
|
65
|
+
)
|
|
49
66
|
], WebAuthCredential.prototype, "updatedAt", void 0);
|
|
67
|
+
tslib_1.__decorate([
|
|
68
|
+
(0, typeorm_1.DeleteDateColumn)(),
|
|
69
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'When the credential was removed.' }),
|
|
70
|
+
tslib_1.__metadata("design:type", Date)
|
|
71
|
+
], WebAuthCredential.prototype, "deletedAt", void 0);
|
|
50
72
|
tslib_1.__decorate([
|
|
51
73
|
(0, typeorm_1.ManyToOne)(type => user_js_1.User, { nullable: true }),
|
|
52
74
|
(0, type_graphql_1.Field)(type => user_js_1.User, { nullable: true, description: 'The user who created the credential.' }),
|
|
@@ -67,7 +89,7 @@ tslib_1.__decorate([
|
|
|
67
89
|
], WebAuthCredential.prototype, "updaterId", void 0);
|
|
68
90
|
exports.WebAuthCredential = WebAuthCredential = tslib_1.__decorate([
|
|
69
91
|
(0, typeorm_1.Entity)(),
|
|
70
|
-
(0, typeorm_1.Index)('ix_web_auth_credential_0', (webAuthCredential) => [webAuthCredential.user, webAuthCredential.credentialId], { unique: true }),
|
|
92
|
+
(0, typeorm_1.Index)('ix_web_auth_credential_0', (webAuthCredential) => [webAuthCredential.user, webAuthCredential.credentialId], { unique: true, where: '"deleted_at" IS NULL' }),
|
|
71
93
|
(0, type_graphql_1.ObjectType)({ description: 'Represents a WebAuthn credential for passwordless authentication.' })
|
|
72
94
|
], WebAuthCredential);
|
|
73
95
|
//# sourceMappingURL=web-auth-credential.js.map
|