@things-factory/auth-base 10.1.205 → 10.1.208

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.
Files changed (43) hide show
  1. package/dist-server/controllers/passkey.d.ts +35 -0
  2. package/dist-server/controllers/passkey.js +182 -0
  3. package/dist-server/controllers/passkey.js.map +1 -0
  4. package/dist-server/controllers/signin.d.ts +24 -0
  5. package/dist-server/controllers/signin.js +63 -39
  6. package/dist-server/controllers/signin.js.map +1 -1
  7. package/dist-server/middlewares/index.d.ts +0 -1
  8. package/dist-server/middlewares/index.js +0 -1
  9. package/dist-server/middlewares/index.js.map +1 -1
  10. package/dist-server/router/auth-signin-router.js +2 -1
  11. package/dist-server/router/auth-signin-router.js.map +1 -1
  12. package/dist-server/router/webauthn-router.js +55 -93
  13. package/dist-server/router/webauthn-router.js.map +1 -1
  14. package/dist-server/service/access-request/index.d.ts +1 -1
  15. package/dist-server/service/audit/audit-kinds.js +3 -1
  16. package/dist-server/service/audit/audit-kinds.js.map +1 -1
  17. package/dist-server/service/audit/index.d.ts +1 -1
  18. package/dist-server/service/domain-link/index.d.ts +1 -1
  19. package/dist-server/service/domain-owner/index.d.ts +1 -1
  20. package/dist-server/service/index.d.ts +1 -1
  21. package/dist-server/service/index.js +2 -1
  22. package/dist-server/service/index.js.map +1 -1
  23. package/dist-server/service/open-fields.js +4 -1
  24. package/dist-server/service/open-fields.js.map +1 -1
  25. package/dist-server/service/role-template/index.d.ts +1 -1
  26. package/dist-server/service/web-auth-credential/index.d.ts +3 -0
  27. package/dist-server/service/web-auth-credential/index.js +4 -1
  28. package/dist-server/service/web-auth-credential/index.js.map +1 -1
  29. package/dist-server/service/web-auth-credential/web-auth-credential-mutation.d.ts +7 -0
  30. package/dist-server/service/web-auth-credential/web-auth-credential-mutation.js +67 -0
  31. package/dist-server/service/web-auth-credential/web-auth-credential-mutation.js.map +1 -0
  32. package/dist-server/service/web-auth-credential/web-auth-credential-query.d.ts +5 -0
  33. package/dist-server/service/web-auth-credential/web-auth-credential-query.js +29 -0
  34. package/dist-server/service/web-auth-credential/web-auth-credential-query.js.map +1 -0
  35. package/dist-server/service/web-auth-credential/web-auth-credential.d.ts +4 -0
  36. package/dist-server/service/web-auth-credential/web-auth-credential.js +24 -2
  37. package/dist-server/service/web-auth-credential/web-auth-credential.js.map +1 -1
  38. package/dist-server/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +5 -5
  40. package/tests/audit-db.test.ts +4 -4
  41. package/tests/passkey-authenticator.ts +104 -0
  42. package/tests/passkey-db.test.ts +286 -0
  43. package/translations/ko.json +2 -2
@@ -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 server_1 = require("@simplewebauthn/server");
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 webauthn_middleware_js_1 = require("../middlewares/webauthn-middleware.js");
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
- // Generate authentication challenge for the currently logged-in user
16
- exports.webAuthnGlobalPrivateRouter.get('/auth/verify-webauthn/challenge', async (context, next) => {
17
- const { user } = context.state;
18
- const rpID = context.hostname;
19
- if (!user) {
20
- context.status = 401;
21
- context.body = { error: 'User not authenticated' };
22
- return;
23
- }
24
- const webAuthCredentials = await (0, shell_1.getRepository)(web_auth_credential_js_1.WebAuthCredential).find({
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
- const options = await (0, server_1.generateAuthenticationOptions)({
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
- // Verify biometric authentication
44
- exports.webAuthnGlobalPrivateRouter.post('/auth/verify-webauthn',
45
- /* reuse webauthn-login as webauthn-verify strategy */
46
- (0, webauthn_middleware_js_1.createWebAuthnMiddleware)('webauthn-login'), async (context, next) => {
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
- // Generate registration challenge for the currently logged-in user
62
- exports.webAuthnGlobalPrivateRouter.get('/auth/register-webauthn/challenge', async (context, next) => {
63
- const { user } = context.state;
64
- const rpID = context.hostname;
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
- // Verify registration
96
- exports.webAuthnGlobalPrivateRouter.post('/auth/verify-registration', (0, webauthn_middleware_js_1.createWebAuthnMiddleware)('webauthn-register'));
97
- // Generate sign-in challenge
98
- exports.webAuthnGlobalPublicRouter.get('/auth/signin-webauthn/challenge', async (context, next) => {
99
- const rpID = context.hostname;
100
- const options = await (0, server_1.generateAuthenticationOptions)({
101
- rpID,
102
- userVerification: 'preferred'
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
- context.session.challenge = options.challenge;
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
- // Sign in with biometric authentication
108
- exports.webAuthnGlobalPublicRouter.post('/auth/signin-webauthn', (0, webauthn_middleware_js_1.createWebAuthnMiddleware)('webauthn-login'), async (context, next) => {
109
- const { domain, user } = context.state;
110
- const { request } = context;
111
- const { body: reqBody } = request;
112
- const token = await user.sign({ subdomain: domain?.subdomain });
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
- var redirectURL = `/auth/checkin${domain ? '/' + domain.subdomain : ''}?redirect_to=${encodeURIComponent(reqBody.redirectTo || '/')}`;
115
- /* Due to the two-step interaction, it will be processed by fetch(...) in the browser, so it cannot be handled with a redirect(3xx) response. Therefore, respond with redirectURL as data. */
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":"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","sourcesContent":["import Router from 'koa-router'\nimport { getRepository } from '@things-factory/shell'\nimport { appPackage } from '@things-factory/env'\n\nimport {\n PublicKeyCredentialCreationOptionsJSON,\n generateRegistrationOptions,\n generateAuthenticationOptions\n} from '@simplewebauthn/server'\n\nimport { WebAuthCredential } from '../service/web-auth-credential/web-auth-credential.js'\nimport { setAccessTokenCookie } from '../utils/access-token-cookie.js'\nimport { createWebAuthnMiddleware } from '../middlewares/webauthn-middleware.js'\n\nexport const webAuthnGlobalPublicRouter = new Router()\nexport const webAuthnGlobalPrivateRouter = new Router()\n\nconst { name: rpName } = appPackage as any\n\n// Generate authentication challenge for the currently logged-in user\nwebAuthnGlobalPrivateRouter.get('/auth/verify-webauthn/challenge', async (context, next) => {\n const { user } = context.state\n const rpID = context.hostname\n\n if (!user) {\n context.status = 401\n context.body = { error: 'User not authenticated' }\n return\n }\n\n const webAuthCredentials = await getRepository(WebAuthCredential).find({\n where: { user: { id: user.id } }\n })\n\n if (webAuthCredentials.length === 0) {\n context.status = 400\n context.body = { error: 'No biometric credentials registered for this user' }\n return\n }\n\n const options = await generateAuthenticationOptions({\n rpID,\n userVerification: 'preferred',\n allowCredentials: webAuthCredentials.map(credential => ({\n id: credential.credentialId,\n type: 'public-key'\n }))\n })\n\n context.session.challenge = options.challenge\n context.body = options\n})\n\n// Verify biometric authentication\nwebAuthnGlobalPrivateRouter.post(\n '/auth/verify-webauthn',\n /* reuse webauthn-login as webauthn-verify strategy */\n createWebAuthnMiddleware('webauthn-login'),\n async (context, next) => {\n const { user } = context.state\n const { request } = context\n const { body: reqBody } = request\n\n if (!user) {\n context.status = 401\n context.body = { verified: false, message: 'User not authenticated' }\n return\n }\n\n context.body = {\n verified: true,\n message: 'Biometric authentication successful'\n }\n\n await next()\n }\n)\n\n// Generate registration challenge for the currently logged-in user\nwebAuthnGlobalPrivateRouter.get('/auth/register-webauthn/challenge', async (context, next) => {\n const { user } = context.state\n const rpID = context.hostname\n\n const webAuthCredentials = await getRepository(WebAuthCredential).find({\n where: {\n user: { id: user.id }\n }\n })\n\n const options: PublicKeyCredentialCreationOptionsJSON = await generateRegistrationOptions({\n rpName,\n rpID,\n userName: user.email,\n userDisplayName: user.name,\n // Don't prompt users for additional information about the authenticator\n // (Recommended for smoother UX)\n attestationType: 'none',\n // Prevent users from re-registering existing authenticators\n excludeCredentials: webAuthCredentials.map(credential => ({\n id: credential.credentialId\n // Optional\n // transports: credential.transports\n })),\n authenticatorSelection: {\n // Defaults\n residentKey: 'preferred',\n userVerification: 'preferred',\n // Optional\n authenticatorAttachment: 'platform'\n }\n })\n\n context.session.challenge = options.challenge\n context.body = options\n})\n\n// Verify registration\nwebAuthnGlobalPrivateRouter.post('/auth/verify-registration', createWebAuthnMiddleware('webauthn-register'))\n\n// Generate sign-in challenge\nwebAuthnGlobalPublicRouter.get('/auth/signin-webauthn/challenge', async (context, next) => {\n const rpID = context.hostname\n\n const options = await generateAuthenticationOptions({\n rpID,\n userVerification: 'preferred'\n })\n\n context.session.challenge = options.challenge\n context.body = options\n})\n\n// Sign in with biometric authentication\nwebAuthnGlobalPublicRouter.post(\n '/auth/signin-webauthn',\n createWebAuthnMiddleware('webauthn-login'),\n async (context, next) => {\n const { domain, user } = context.state\n const { request } = context\n const { body: reqBody } = request\n\n const token = await user.sign({ subdomain: domain?.subdomain })\n setAccessTokenCookie(context, token)\n\n var redirectURL = `/auth/checkin${domain ? '/' + domain.subdomain : ''}?redirect_to=${encodeURIComponent(reqBody.redirectTo || '/')}`\n\n /* Due to the two-step interaction, it will be processed by fetch(...) in the browser, so it cannot be handled with a redirect(3xx) response. Therefore, respond with redirectURL as data. */\n context.body = { redirectURL, verified: true }\n\n await next()\n }\n)\n"]}
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"]}
@@ -2,4 +2,4 @@ import { AccessRequest } from './access-request.js';
2
2
  import { AccessRequestMutation } from './access-request-mutation.js';
3
3
  import { AccessRequestQuery } from './access-request-query.js';
4
4
  export declare const entities: (typeof AccessRequest)[];
5
- export declare const resolvers: (typeof AccessRequestMutation | typeof AccessRequestQuery)[];
5
+ export declare const resolvers: (typeof AccessRequestQuery | typeof AccessRequestMutation)[];
@@ -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
- registerAuditKind('auth.signin');
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":"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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. */\nregisterAuditKind('auth.signin')\nregisterAuditKind('auth.checkin')\nregisterAuditKind('auth.signout')\nregisterAuditKind('password.change')\nregisterAuditKind('password.reset-by-admin')\nregisterAuditKind('credential.issue')\nregisterAuditKind('webauthn.register')\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"]}
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"]}
@@ -1,6 +1,6 @@
1
1
  import { AuditAction } from './audit-action.js';
2
2
  import { AuditQuery } from './audit-query.js';
3
3
  import { AuditRequest } from './audit-request.js';
4
- export declare const entities: (typeof AuditAction | typeof AuditRequest)[];
4
+ export declare const entities: (typeof AuditRequest | typeof AuditAction)[];
5
5
  export declare const resolvers: (typeof AuditQuery)[];
6
6
  export declare const subscribers: (typeof import("./audit-subscriber.js").GrantAuditSubscriber | typeof import("./audit-subscriber.js").UserAuditSubscriber | typeof import("./audit-subscriber.js").DomainOwnerAuditSubscriber | typeof import("./audit-subscriber.js").InvitationAuditSubscriber | typeof import("./audit-subscriber.js").WebAuthCredentialAuditSubscriber)[];
@@ -2,5 +2,5 @@ import { DomainLink } from './domain-link.js';
2
2
  import { DomainLinkQuery } from './domain-link-query.js';
3
3
  import { DomainLinkMutation } from './domain-link-mutation.js';
4
4
  export declare const entities: (typeof DomainLink)[];
5
- export declare const resolvers: (typeof DomainLinkQuery | typeof DomainLinkMutation)[];
5
+ export declare const resolvers: (typeof DomainLinkMutation | typeof DomainLinkQuery)[];
6
6
  export declare const subscribers: any[];
@@ -2,4 +2,4 @@ import { DomainOwner } from './domain-owner.js';
2
2
  import { DomainOwnerQuery } from './domain-owner-query.js';
3
3
  import { DomainOwnerMutation } from './domain-owner-mutation.js';
4
4
  export declare const entities: (typeof DomainOwner)[];
5
- export declare const resolvers: (typeof DomainOwnerQuery | typeof DomainOwnerMutation)[];
5
+ export declare const resolvers: (typeof DomainOwnerMutation | typeof DomainOwnerQuery)[];
@@ -38,7 +38,7 @@ export * from './role/role-types.js';
38
38
  export * from './user/user-types.js';
39
39
  export * from './domain-link/domain-link-types.js';
40
40
  export * from './access-request/access-request-types.js';
41
- export declare const entities: (typeof import("./web-auth-credential/web-auth-credential.js").WebAuthCredential | typeof import("./user/user.js").User | typeof import("./auth-provider/auth-provider.js").AuthProvider | typeof import("./users-auth-providers/users-auth-providers.js").UsersAuthProviders | typeof import("./domain-owner/domain-owner.js").DomainOwner | typeof import("./granted-role/granted-role.js").GrantedRole | typeof import("./role/role.js").Role | typeof import("./privilege/privilege.js").Privilege | typeof import("./audit/audit-action.js").AuditAction | typeof import("./audit/audit-request.js").AuditRequest | typeof import("./password-history/password-history.js").PasswordHistory | typeof import("./verification-token/verification-token.js").VerificationToken | typeof import("./verification-token/verification-token.js").VerificationTokenType | typeof import("./invitation/invitation.js").Invitation | typeof import("./application/application.js").Application | typeof import("./access-request/access-request.js").AccessRequest | typeof import("./appliance/appliance.js").Appliance | typeof import("./partner/partner.js").Partner | typeof import("./domain-link/domain-link.js").DomainLink)[];
41
+ export declare const entities: (typeof import("./invitation/invitation.js").Invitation | typeof import("./domain-link/domain-link.js").DomainLink | typeof import("./web-auth-credential/web-auth-credential.js").WebAuthCredential | typeof import("./users-auth-providers/users-auth-providers.js").UsersAuthProviders | typeof import("./auth-provider/auth-provider.js").AuthProvider | typeof import("./application/application.js").Application | typeof import("./appliance/appliance.js").Appliance | typeof import("./privilege/privilege.js").Privilege | typeof import("./role/role.js").Role | typeof import("./user/user.js").User | typeof import("./partner/partner.js").Partner | typeof import("./granted-role/granted-role.js").GrantedRole | typeof import("./password-history/password-history.js").PasswordHistory | typeof import("./verification-token/verification-token.js").VerificationToken | typeof import("./verification-token/verification-token.js").VerificationTokenType | typeof import("./audit/audit-request.js").AuditRequest | typeof import("./audit/audit-action.js").AuditAction | typeof import("./domain-owner/domain-owner.js").DomainOwner | typeof import("./access-request/access-request.js").AccessRequest)[];
42
42
  export declare const subscribers: (typeof import("./audit/audit-subscriber.js").GrantAuditSubscriber | typeof import("./audit/audit-subscriber.js").UserAuditSubscriber | typeof import("./audit/audit-subscriber.js").DomainOwnerAuditSubscriber | typeof import("./audit/audit-subscriber.js").InvitationAuditSubscriber | typeof import("./audit/audit-subscriber.js").WebAuthCredentialAuditSubscriber)[];
43
43
  export declare const schema: {
44
44
  typeDefs: {
@@ -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,8DAAsF;AACtF,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;KAC1B;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 } 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 ],\n directives: {\n privilege: privilegeDirectiveResolver\n }\n}\n"]}
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;KACjG;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 }\n}\n"]}
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"]}
@@ -2,4 +2,4 @@ import { RoleTemplateGapResolver } from './role-template-gap-resolver.js';
2
2
  import { RoleTemplateMutation } from './role-template-mutation.js';
3
3
  import { RoleTemplateQuery } from './role-template-query.js';
4
4
  export declare const entities: any[];
5
- export declare const resolvers: (typeof RoleTemplateGapResolver | typeof RoleTemplateMutation | typeof RoleTemplateQuery)[];
5
+ export declare const resolvers: (typeof RoleTemplateQuery | typeof RoleTemplateMutation | typeof RoleTemplateGapResolver)[];
@@ -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 WebAuthCredentialQuery | typeof WebAuthCredentialMutation)[];
@@ -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;AAE/C,QAAA,QAAQ,GAAG,CAAC,0CAAiB,CAAC,CAAA","sourcesContent":["import { WebAuthCredential } from './web-auth-credential.js'\n\nexport const entities = [WebAuthCredential]\n"]}
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"]}