@authup/server-core 1.0.0-beta.61 → 1.0.0-beta.62

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 (133) hide show
  1. package/dist/adapters/database/domains/client-scope/entity.d.ts.map +1 -1
  2. package/dist/adapters/database/domains/client-scope/entity.mjs +16 -8
  3. package/dist/adapters/database/domains/client-scope/entity.mjs.map +1 -1
  4. package/dist/adapters/database/domains/identity-provider-account/entity.d.ts.map +1 -1
  5. package/dist/adapters/database/domains/identity-provider-account/entity.mjs +1 -1
  6. package/dist/adapters/database/domains/identity-provider-account/entity.mjs.map +1 -1
  7. package/dist/adapters/database/migrations/mysql/1786631686318-SortIndexesAndAccountUniqueness.d.ts +50 -0
  8. package/dist/adapters/database/migrations/mysql/1786631686318-SortIndexesAndAccountUniqueness.d.ts.map +1 -0
  9. package/dist/adapters/database/migrations/mysql/1786631686318-SortIndexesAndAccountUniqueness.mjs +86 -0
  10. package/dist/adapters/database/migrations/mysql/1786631686318-SortIndexesAndAccountUniqueness.mjs.map +1 -0
  11. package/dist/adapters/database/migrations/postgres/1786631686318-SortIndexesAndAccountUniqueness.d.ts +42 -0
  12. package/dist/adapters/database/migrations/postgres/1786631686318-SortIndexesAndAccountUniqueness.d.ts.map +1 -0
  13. package/dist/adapters/database/migrations/postgres/1786631686318-SortIndexesAndAccountUniqueness.mjs +80 -0
  14. package/dist/adapters/database/migrations/postgres/1786631686318-SortIndexesAndAccountUniqueness.mjs.map +1 -0
  15. package/dist/adapters/http/adapters/oauth2/grant-types/utils/credentials.mjs +1 -1
  16. package/dist/adapters/http/adapters/oauth2/grant-types/utils/guess.mjs +1 -1
  17. package/dist/adapters/http/controllers/entities/client/module.mjs +2 -2
  18. package/dist/adapters/http/controllers/entities/identity-provider/module.d.ts +3 -5
  19. package/dist/adapters/http/controllers/entities/identity-provider/module.d.ts.map +1 -1
  20. package/dist/adapters/http/controllers/entities/identity-provider/module.mjs +54 -82
  21. package/dist/adapters/http/controllers/entities/identity-provider/module.mjs.map +1 -1
  22. package/dist/adapters/http/controllers/entities/identity-provider/types.d.ts +6 -4
  23. package/dist/adapters/http/controllers/entities/identity-provider/types.d.ts.map +1 -1
  24. package/dist/adapters/http/controllers/entities/realm/module.mjs +2 -2
  25. package/dist/adapters/http/controllers/entities/session/module.mjs +2 -2
  26. package/dist/adapters/http/controllers/entities/user/module.mjs +2 -2
  27. package/dist/adapters/http/controllers/entities/user-authenticator/module.mjs +2 -2
  28. package/dist/adapters/http/controllers/workflows/authorize/module.d.ts.map +1 -1
  29. package/dist/adapters/http/controllers/workflows/authorize/module.mjs +9 -5
  30. package/dist/adapters/http/controllers/workflows/authorize/module.mjs.map +1 -1
  31. package/dist/adapters/http/controllers/workflows/token/utils/extract.mjs +2 -1
  32. package/dist/adapters/http/controllers/workflows/token/utils/extract.mjs.map +1 -1
  33. package/dist/adapters/http/middleware/built-in/assets.mjs +1 -1
  34. package/dist/adapters/http/request/helpers/param-id.d.ts.map +1 -1
  35. package/dist/adapters/http/request/helpers/param-id.mjs +2 -1
  36. package/dist/adapters/http/request/helpers/param-id.mjs.map +1 -1
  37. package/dist/adapters/http/ui/account-console/module.mjs +1 -1
  38. package/dist/adapters/http/ui/auth-console/http-client.mjs +1 -1
  39. package/dist/adapters/http/ui/auth-console/module.mjs +1 -1
  40. package/dist/adapters/http/ui/console-packages/module.d.ts +1 -1
  41. package/dist/adapters/http/ui/console-packages/module.mjs +2 -2
  42. package/dist/adapters/http/ui/console-packages/module.mjs.map +1 -1
  43. package/dist/adapters/http/ui/shared/html.d.ts.map +1 -1
  44. package/dist/adapters/http/ui/shared/html.mjs +1 -0
  45. package/dist/adapters/http/ui/shared/html.mjs.map +1 -1
  46. package/dist/app/modules/config/read/env.mjs +1 -1
  47. package/dist/app/modules/database/repositories/event/repository.d.ts +2 -2
  48. package/dist/app/modules/database/repositories/event/repository.d.ts.map +1 -1
  49. package/dist/app/modules/database/repositories/event/repository.mjs +23 -6
  50. package/dist/app/modules/database/repositories/event/repository.mjs.map +1 -1
  51. package/dist/app/modules/database/repositories/identity-provider-account/repository.d.ts.map +1 -1
  52. package/dist/app/modules/database/repositories/identity-provider-account/repository.mjs +9 -1
  53. package/dist/app/modules/database/repositories/identity-provider-account/repository.mjs.map +1 -1
  54. package/dist/app/modules/http/modules/controller.d.ts.map +1 -1
  55. package/dist/app/modules/http/modules/controller.mjs +16 -5
  56. package/dist/app/modules/http/modules/controller.mjs.map +1 -1
  57. package/dist/app/modules/identity/module.d.ts.map +1 -1
  58. package/dist/app/modules/identity/module.mjs +6 -3
  59. package/dist/app/modules/identity/module.mjs.map +1 -1
  60. package/dist/app/modules/identity/repositories/user.d.ts +1 -0
  61. package/dist/app/modules/identity/repositories/user.d.ts.map +1 -1
  62. package/dist/app/modules/identity/repositories/user.mjs +3 -0
  63. package/dist/app/modules/identity/repositories/user.mjs.map +1 -1
  64. package/dist/app/modules/oauth2/repositories/authorize/state/repository.d.ts +1 -2
  65. package/dist/app/modules/oauth2/repositories/authorize/state/repository.d.ts.map +1 -1
  66. package/dist/app/modules/oauth2/repositories/authorize/state/repository.mjs +7 -16
  67. package/dist/app/modules/oauth2/repositories/authorize/state/repository.mjs.map +1 -1
  68. package/dist/app/modules/runtime/module.mjs +2 -2
  69. package/dist/cli/commands/migration.mjs +1 -1
  70. package/dist/core/entities/client-scope/schema.d.ts.map +1 -1
  71. package/dist/core/entities/client-scope/schema.mjs +16 -1
  72. package/dist/core/entities/client-scope/schema.mjs.map +1 -1
  73. package/dist/core/entities/event/constants.d.ts +9 -0
  74. package/dist/core/entities/event/constants.d.ts.map +1 -1
  75. package/dist/core/entities/event/constants.mjs +9 -1
  76. package/dist/core/entities/event/constants.mjs.map +1 -1
  77. package/dist/core/entities/event/index.mjs +2 -2
  78. package/dist/core/entities/event/types.d.ts +11 -2
  79. package/dist/core/entities/event/types.d.ts.map +1 -1
  80. package/dist/core/entities/identity-provider-account/types.d.ts +6 -0
  81. package/dist/core/entities/identity-provider-account/types.d.ts.map +1 -1
  82. package/dist/core/entities/identity-provider-account/types.mjs.map +1 -1
  83. package/dist/core/entities/index.mjs +2 -2
  84. package/dist/core/identity/entities/user/types.d.ts +1 -0
  85. package/dist/core/identity/entities/user/types.d.ts.map +1 -1
  86. package/dist/core/identity/index.mjs +10 -10
  87. package/dist/core/identity/provider/account/module.d.ts +3 -0
  88. package/dist/core/identity/provider/account/module.d.ts.map +1 -1
  89. package/dist/core/identity/provider/account/module.mjs +54 -27
  90. package/dist/core/identity/provider/account/module.mjs.map +1 -1
  91. package/dist/core/identity/provider/account/types.d.ts +7 -0
  92. package/dist/core/identity/provider/account/types.d.ts.map +1 -1
  93. package/dist/core/identity/provider/authentication/index.mjs +1 -1
  94. package/dist/core/identity/provider/index.mjs +10 -10
  95. package/dist/core/index.mjs +14 -12
  96. package/dist/core/oauth2/authorization/code-request/verifier/module.d.ts.map +1 -1
  97. package/dist/core/oauth2/authorization/code-request/verifier/module.mjs +7 -1
  98. package/dist/core/oauth2/authorization/code-request/verifier/module.mjs.map +1 -1
  99. package/dist/core/oauth2/authorization/state/module.mjs +1 -2
  100. package/dist/core/oauth2/authorization/state/module.mjs.map +1 -1
  101. package/dist/core/oauth2/authorization/state/types.d.ts +5 -2
  102. package/dist/core/oauth2/authorization/state/types.d.ts.map +1 -1
  103. package/dist/core/oauth2/end-session/service.d.ts.map +1 -1
  104. package/dist/core/oauth2/end-session/service.mjs +1 -0
  105. package/dist/core/oauth2/end-session/service.mjs.map +1 -1
  106. package/dist/core/oauth2/federated-login/index.d.ts +3 -0
  107. package/dist/core/oauth2/federated-login/index.d.ts.map +1 -0
  108. package/dist/core/oauth2/federated-login/index.mjs +6 -0
  109. package/dist/core/oauth2/federated-login/module.d.ts +20 -0
  110. package/dist/core/oauth2/federated-login/module.d.ts.map +1 -0
  111. package/dist/core/oauth2/federated-login/module.mjs +137 -0
  112. package/dist/core/oauth2/federated-login/module.mjs.map +1 -0
  113. package/dist/core/oauth2/federated-login/types.d.ts +138 -0
  114. package/dist/core/oauth2/federated-login/types.d.ts.map +1 -0
  115. package/dist/core/oauth2/federated-login/types.mjs +31 -0
  116. package/dist/core/oauth2/federated-login/types.mjs.map +1 -0
  117. package/dist/core/oauth2/index.d.ts +1 -0
  118. package/dist/core/oauth2/index.d.ts.map +1 -1
  119. package/dist/core/oauth2/index.mjs +4 -1
  120. package/dist/core/provisioning/entities/policy/validator.d.ts.map +1 -1
  121. package/dist/core/provisioning/entities/policy/validator.mjs +2 -1
  122. package/dist/core/provisioning/entities/policy/validator.mjs.map +1 -1
  123. package/dist/core/provisioning/entities/realm/wildcard-validator.d.ts.map +1 -1
  124. package/dist/core/provisioning/entities/realm/wildcard-validator.mjs +2 -1
  125. package/dist/core/provisioning/entities/realm/wildcard-validator.mjs.map +1 -1
  126. package/dist/core/security/policy/evaluator.mjs.map +1 -1
  127. package/dist/node_modules/@ebec/core/dist/index.mjs +130 -0
  128. package/dist/node_modules/@ebec/core/dist/index.mjs.map +1 -0
  129. package/dist/swagger.json +30 -2
  130. package/dist/utils/error.d.ts.map +1 -1
  131. package/dist/utils/error.mjs +5 -6
  132. package/dist/utils/error.mjs.map +1 -1
  133. package/package.json +28 -28
@@ -0,0 +1,138 @@
1
+ import type { OAuth2AuthorizationCodeRequest, OAuth2IdentityProvider, OpenIDIdentityProvider, User } from '@authup/core-kit';
2
+ import type { OAuth2ErrorCode } from '@authup/specs';
3
+ import type { Logger } from '@authup/server-kit';
4
+ import type { IIdentityProviderAccountManager } from '../../identity/provider/account/types.ts';
5
+ import type { IOAuth2Authenticator } from '../../identity/provider/authentication/protocols/index.ts';
6
+ import type { IEventService, IRealmRepository } from '../../entities/index.ts';
7
+ import type { IOAuth2AccessPolicyEvaluator } from '../access-policy/index.ts';
8
+ import type { IOAuth2AuthorizationCodeIssuer, IOAuth2AuthorizationCodeRequestVerifier } from '../authorization/index.ts';
9
+ export type OAuth2FederatedLoginCompleteInput = {
10
+ /**
11
+ * The provider the callback belongs to, already resolved and
12
+ * protocol-checked by the caller.
13
+ */
14
+ provider: OAuth2IdentityProvider | OpenIDIdentityProvider;
15
+ /**
16
+ * The RP's original authorization request, as `authorize-out` stored
17
+ * it on the state blob. It is re-verified here rather than trusted.
18
+ */
19
+ codeRequest: OAuth2AuthorizationCodeRequest;
20
+ /**
21
+ * The provider's single-use authorization code.
22
+ */
23
+ code: string;
24
+ request?: {
25
+ ipAddress?: string | null;
26
+ userAgent?: string | null;
27
+ };
28
+ };
29
+ /**
30
+ * Why a completion was refused. Every value is a REFUSAL — a decision the
31
+ * person at the browser has to be told about on the hosted login page —
32
+ * never a server failure, which keeps throwing.
33
+ */
34
+ export declare enum OAuth2FederatedLoginRefusal {
35
+ /**
36
+ * The stored code request no longer verifies (client deactivated or
37
+ * unknown, grant allowlist, scopes, redirect_uri mismatch). The hosted
38
+ * page re-runs the same verifier and renders the same refusal, so
39
+ * nothing is echoed and the bounce carries no marker.
40
+ */
41
+ CODE_REQUEST = "codeRequest",
42
+ /**
43
+ * The provider was disabled while the person was away at it.
44
+ */
45
+ PROVIDER_DISABLED = "providerDisabled",
46
+ /**
47
+ * The linked local user is inactive. The local login path refuses one
48
+ * too, and a federated login must not be the way around that.
49
+ */
50
+ USER_INACTIVE = "userInactive",
51
+ /**
52
+ * The client's access policy denied the identity (plan 052).
53
+ */
54
+ ACCESS_DENIED = "accessDenied"
55
+ }
56
+ export type OAuth2FederatedLoginRefusedResult = {
57
+ kind: 'refused';
58
+ refusal: OAuth2FederatedLoginRefusal;
59
+ /**
60
+ * The marker the hosted authorize page maps onto its own copy, when
61
+ * the refusal has one. Absent means "render the request again", which
62
+ * re-runs the verifier and states the reason itself.
63
+ */
64
+ error?: `${OAuth2ErrorCode}`;
65
+ /**
66
+ * The request to re-render. The verified copy when re-verification got
67
+ * that far, else the stored one.
68
+ */
69
+ codeRequest: OAuth2AuthorizationCodeRequest;
70
+ };
71
+ export type OAuth2FederatedLoginIssuedResult = {
72
+ kind: 'issued';
73
+ /**
74
+ * The verified redirect target of the RP. Its scheme passed
75
+ * `isSafeRedirectURLScheme`, so a caller may navigate it.
76
+ */
77
+ redirectUri: string;
78
+ /**
79
+ * The authorization code id the RP redeems at `/token`.
80
+ */
81
+ code: string;
82
+ state?: string;
83
+ /**
84
+ * The verified request, for a caller that has to re-render the hosted
85
+ * page alongside the redirect (the custom-scheme interstitial).
86
+ */
87
+ codeRequest: OAuth2AuthorizationCodeRequest;
88
+ client: {
89
+ id: string;
90
+ name: string;
91
+ displayName: string | null;
92
+ };
93
+ };
94
+ export type OAuth2FederatedLoginCompleteResult = OAuth2FederatedLoginRefusedResult | OAuth2FederatedLoginIssuedResult;
95
+ /**
96
+ * Completes the RP's original authorization request after an external
97
+ * provider sent the browser back (issue #3446). It owns the refusal ladder
98
+ * — realm match, code-request re-verification, redirect-scheme gate,
99
+ * provider and user state, access policy — and mints the authup code.
100
+ *
101
+ * Deliberately transport-free: it decides WHAT the answer is, and returns
102
+ * it as a discriminated result. The adapter decides how that answer
103
+ * reaches the browser (a redirect, or the interstitial page a custom
104
+ * scheme needs), because only the adapter knows the difference.
105
+ */
106
+ export interface IOAuth2FederatedLoginService {
107
+ complete(input: OAuth2FederatedLoginCompleteInput): Promise<OAuth2FederatedLoginCompleteResult>;
108
+ }
109
+ export type OAuth2FederatedLoginServiceOptions = {
110
+ /**
111
+ * The public base URL. Reaches the provider authenticator, which
112
+ * builds the callback URL the external token exchange must repeat.
113
+ */
114
+ baseURL: string;
115
+ };
116
+ /**
117
+ * Builds the authenticator that exchanges the provider's code. Injected
118
+ * rather than constructed inline so the refusal ladder can be exercised
119
+ * without an external provider; it defaults to the real preset/protocol
120
+ * factory.
121
+ */
122
+ export type OAuth2FederatedLoginAuthenticatorFactory = (provider: OAuth2IdentityProvider | OpenIDIdentityProvider, options: {
123
+ baseURL: string;
124
+ clientId?: string;
125
+ logger?: Logger;
126
+ }) => IOAuth2Authenticator<User>;
127
+ export type OAuth2FederatedLoginServiceContext = {
128
+ options: OAuth2FederatedLoginServiceOptions;
129
+ accountManager: IIdentityProviderAccountManager;
130
+ realmRepository: IRealmRepository;
131
+ codeRequestVerifier: IOAuth2AuthorizationCodeRequestVerifier;
132
+ codeIssuer: IOAuth2AuthorizationCodeIssuer;
133
+ accessPolicyEvaluator?: IOAuth2AccessPolicyEvaluator;
134
+ eventService?: IEventService;
135
+ logger?: Logger;
136
+ authenticatorFactory?: OAuth2FederatedLoginAuthenticatorFactory;
137
+ };
138
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/core/oauth2/federated-login/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region src/core/oauth2/federated-login/types.ts
5
+ /**
6
+ * Why a completion was refused. Every value is a REFUSAL — a decision the
7
+ * person at the browser has to be told about on the hosted login page —
8
+ * never a server failure, which keeps throwing.
9
+ */ var OAuth2FederatedLoginRefusal = /*#__PURE__*/ function(OAuth2FederatedLoginRefusal) {
10
+ /**
11
+ * The stored code request no longer verifies (client deactivated or
12
+ * unknown, grant allowlist, scopes, redirect_uri mismatch). The hosted
13
+ * page re-runs the same verifier and renders the same refusal, so
14
+ * nothing is echoed and the bounce carries no marker.
15
+ */ OAuth2FederatedLoginRefusal["CODE_REQUEST"] = "codeRequest";
16
+ /**
17
+ * The provider was disabled while the person was away at it.
18
+ */ OAuth2FederatedLoginRefusal["PROVIDER_DISABLED"] = "providerDisabled";
19
+ /**
20
+ * The linked local user is inactive. The local login path refuses one
21
+ * too, and a federated login must not be the way around that.
22
+ */ OAuth2FederatedLoginRefusal["USER_INACTIVE"] = "userInactive";
23
+ /**
24
+ * The client's access policy denied the identity (plan 052).
25
+ */ OAuth2FederatedLoginRefusal["ACCESS_DENIED"] = "accessDenied";
26
+ return OAuth2FederatedLoginRefusal;
27
+ }({});
28
+ //#endregion
29
+ export { OAuth2FederatedLoginRefusal };
30
+
31
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.mjs","names":["OAuth2FederatedLoginRefusal"],"sources":["../../../../src/core/oauth2/federated-login/types.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type {\n OAuth2AuthorizationCodeRequest,\n OAuth2IdentityProvider,\n OpenIDIdentityProvider, \n User, \n} from '@authup/core-kit';\nimport type { OAuth2ErrorCode } from '@authup/specs';\nimport type { Logger } from '@authup/server-kit';\nimport type { IIdentityProviderAccountManager } from '../../identity/provider/account/types.ts';\nimport type { IOAuth2Authenticator } from '../../identity/provider/authentication/protocols/index.ts';\nimport type { IEventService, IRealmRepository } from '../../entities/index.ts';\nimport type { IOAuth2AccessPolicyEvaluator } from '../access-policy/index.ts';\nimport type {\n IOAuth2AuthorizationCodeIssuer,\n IOAuth2AuthorizationCodeRequestVerifier,\n} from '../authorization/index.ts';\n\nexport type OAuth2FederatedLoginCompleteInput = {\n /**\n * The provider the callback belongs to, already resolved and\n * protocol-checked by the caller.\n */\n provider: OAuth2IdentityProvider | OpenIDIdentityProvider,\n /**\n * The RP's original authorization request, as `authorize-out` stored\n * it on the state blob. It is re-verified here rather than trusted.\n */\n codeRequest: OAuth2AuthorizationCodeRequest,\n /**\n * The provider's single-use authorization code.\n */\n code: string,\n request?: {\n ipAddress?: string | null,\n userAgent?: string | null,\n },\n};\n\n/**\n * Why a completion was refused. Every value is a REFUSAL — a decision the\n * person at the browser has to be told about on the hosted login page —\n * never a server failure, which keeps throwing.\n */\nexport enum OAuth2FederatedLoginRefusal {\n /**\n * The stored code request no longer verifies (client deactivated or\n * unknown, grant allowlist, scopes, redirect_uri mismatch). The hosted\n * page re-runs the same verifier and renders the same refusal, so\n * nothing is echoed and the bounce carries no marker.\n */\n CODE_REQUEST = 'codeRequest',\n /**\n * The provider was disabled while the person was away at it.\n */\n PROVIDER_DISABLED = 'providerDisabled',\n /**\n * The linked local user is inactive. The local login path refuses one\n * too, and a federated login must not be the way around that.\n */\n USER_INACTIVE = 'userInactive',\n /**\n * The client's access policy denied the identity (plan 052).\n */\n ACCESS_DENIED = 'accessDenied',\n}\n\nexport type OAuth2FederatedLoginRefusedResult = {\n kind: 'refused',\n refusal: OAuth2FederatedLoginRefusal,\n /**\n * The marker the hosted authorize page maps onto its own copy, when\n * the refusal has one. Absent means \"render the request again\", which\n * re-runs the verifier and states the reason itself.\n */\n error?: `${OAuth2ErrorCode}`,\n /**\n * The request to re-render. The verified copy when re-verification got\n * that far, else the stored one.\n */\n codeRequest: OAuth2AuthorizationCodeRequest,\n};\n\nexport type OAuth2FederatedLoginIssuedResult = {\n kind: 'issued',\n /**\n * The verified redirect target of the RP. Its scheme passed\n * `isSafeRedirectURLScheme`, so a caller may navigate it.\n */\n redirectUri: string,\n /**\n * The authorization code id the RP redeems at `/token`.\n */\n code: string,\n state?: string,\n /**\n * The verified request, for a caller that has to re-render the hosted\n * page alongside the redirect (the custom-scheme interstitial).\n */\n codeRequest: OAuth2AuthorizationCodeRequest,\n client: {\n id: string,\n name: string,\n displayName: string | null,\n },\n};\n\nexport type OAuth2FederatedLoginCompleteResult = OAuth2FederatedLoginRefusedResult |\n OAuth2FederatedLoginIssuedResult;\n\n/**\n * Completes the RP's original authorization request after an external\n * provider sent the browser back (issue #3446). It owns the refusal ladder\n * — realm match, code-request re-verification, redirect-scheme gate,\n * provider and user state, access policy — and mints the authup code.\n *\n * Deliberately transport-free: it decides WHAT the answer is, and returns\n * it as a discriminated result. The adapter decides how that answer\n * reaches the browser (a redirect, or the interstitial page a custom\n * scheme needs), because only the adapter knows the difference.\n */\nexport interface IOAuth2FederatedLoginService {\n complete(input: OAuth2FederatedLoginCompleteInput) : Promise<OAuth2FederatedLoginCompleteResult>;\n}\n\nexport type OAuth2FederatedLoginServiceOptions = {\n /**\n * The public base URL. Reaches the provider authenticator, which\n * builds the callback URL the external token exchange must repeat.\n */\n baseURL: string,\n};\n\n/**\n * Builds the authenticator that exchanges the provider's code. Injected\n * rather than constructed inline so the refusal ladder can be exercised\n * without an external provider; it defaults to the real preset/protocol\n * factory.\n */\nexport type OAuth2FederatedLoginAuthenticatorFactory = (\n provider: OAuth2IdentityProvider | OpenIDIdentityProvider,\n options: {\n baseURL: string, \n clientId?: string, \n logger?: Logger \n },\n) => IOAuth2Authenticator<User>;\n\nexport type OAuth2FederatedLoginServiceContext = {\n options: OAuth2FederatedLoginServiceOptions,\n\n accountManager: IIdentityProviderAccountManager,\n realmRepository: IRealmRepository,\n codeRequestVerifier: IOAuth2AuthorizationCodeRequestVerifier,\n codeIssuer: IOAuth2AuthorizationCodeIssuer,\n\n accessPolicyEvaluator?: IOAuth2AccessPolicyEvaluator,\n eventService?: IEventService,\n logger?: Logger,\n authenticatorFactory?: OAuth2FederatedLoginAuthenticatorFactory,\n};\n"],"mappings":";;;;;;;;GAkDA,IAAO,8BAAKA,uBAAAA,6BAAAA;;;;;;IAMP,4BAAA,kBAAA;;;IAIA,4BAAA,uBAAA;;;;IAKA,4BAAA,mBAAA;;;IAIA,4BAAA,mBAAA;CAnBOA,OAAAA;AAqBX,EAAA,CAAA,CAAA"}
@@ -2,6 +2,7 @@ export * from './access-policy/index.ts';
2
2
  export * from './authorization/index.ts';
3
3
  export * from './client/index.ts';
4
4
  export * from './end-session/index.ts';
5
+ export * from './federated-login/index.ts';
5
6
  export * from './grant-types/index.ts';
6
7
  export * from './mfa-login/index.ts';
7
8
  export * from './openid/index.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/oauth2/index.ts"],"names":[],"mappings":"AAOA,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/oauth2/index.ts"],"names":[],"mappings":"AAOA,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC"}
@@ -17,6 +17,9 @@ import "./authorization/index.mjs";
17
17
  import { OAuth2EndSessionService } from "./end-session/service.mjs";
18
18
  import { OAuth2EndSessionRequestValidator } from "./end-session/validator.mjs";
19
19
  import "./end-session/index.mjs";
20
+ import { OAuth2FederatedLoginRefusal } from "./federated-login/types.mjs";
21
+ import { OAuth2FederatedLoginService } from "./federated-login/module.mjs";
22
+ import "./federated-login/index.mjs";
20
23
  import { buildOAuth2BearerTokenResponse } from "./response/bearer.mjs";
21
24
  import { OAuth2AuthorizeGrant } from "./grant-types/authorize.mjs";
22
25
  import { ClientCredentialsGrant } from "./grant-types/client-credentials.mjs";
@@ -40,4 +43,4 @@ import { OAuth2TokenRevoker } from "./token/revoker/module.mjs";
40
43
  import { OAuth2TokenSigner } from "./token/signer/module.mjs";
41
44
  import { OAuth2TokenVerifier } from "./token/verifier/module.mjs";
42
45
  import "./token/index.mjs";
43
- export { ClientCredentialsGrant, IdentityGrantType, OAuth2AccessPolicyEvaluator, OAuth2AccessTokenIssuer, OAuth2Authorization, OAuth2AuthorizationCodeIssuer, OAuth2AuthorizationCodeRequestValidator, OAuth2AuthorizationCodeRequestVerifier, OAuth2AuthorizationCodeVerifier, OAuth2AuthorizationStateManager, OAuth2AuthorizeGrant, OAuth2ClientAuthenticator, OAuth2EndSessionRequestValidator, OAuth2EndSessionService, OAuth2MfaLoginService, OAuth2MfaTokenIssuer, OAuth2OpenIDClaimsBuilder, OAuth2OpenIDTokenIssuer, OAuth2RefreshTokenGrant, OAuth2RefreshTokenIssuer, OAuth2ScopeAttributesResolver, OAuth2TokenRevoker, OAuth2TokenSigner, OAuth2TokenVerifier, PasswordGrantType, SESSION_TOKEN_RELATION_MISSING_ERROR_INSTANCE, SessionTokenRelationMissingError, assertClientGrantAllowed, base64URLEncode, buildOAuth2BearerTokenResponse, buildOAuth2CodeChallenge, buildOAuth2TokenHash, deriveAmrAcr, generateOAuth2CodeVerifier, isSessionTokenRelationMissingError };
46
+ export { ClientCredentialsGrant, IdentityGrantType, OAuth2AccessPolicyEvaluator, OAuth2AccessTokenIssuer, OAuth2Authorization, OAuth2AuthorizationCodeIssuer, OAuth2AuthorizationCodeRequestValidator, OAuth2AuthorizationCodeRequestVerifier, OAuth2AuthorizationCodeVerifier, OAuth2AuthorizationStateManager, OAuth2AuthorizeGrant, OAuth2ClientAuthenticator, OAuth2EndSessionRequestValidator, OAuth2EndSessionService, OAuth2FederatedLoginRefusal, OAuth2FederatedLoginService, OAuth2MfaLoginService, OAuth2MfaTokenIssuer, OAuth2OpenIDClaimsBuilder, OAuth2OpenIDTokenIssuer, OAuth2RefreshTokenGrant, OAuth2RefreshTokenIssuer, OAuth2ScopeAttributesResolver, OAuth2TokenRevoker, OAuth2TokenSigner, OAuth2TokenVerifier, PasswordGrantType, SESSION_TOKEN_RELATION_MISSING_ERROR_INSTANCE, SessionTokenRelationMissingError, assertClientGrantAllowed, base64URLEncode, buildOAuth2BearerTokenResponse, buildOAuth2CodeChallenge, buildOAuth2TokenHash, deriveAmrAcr, generateOAuth2CodeVerifier, isSessionTokenRelationMissingError };
@@ -1 +1 @@
1
- {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../../../src/core/provisioning/entities/policy/validator.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,SAAS,EAAiC,MAAM,SAAS,CAAC;AAInE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D,qBAAa,2BAA4B,SAAQ,SAAS,CAAC,wBAAwB,CAAC;IACjE,GAAG,CACd,KAAK,CAAC,EAAE,cAAc,CAAC,wBAAwB,CAAC,EAChD,OAAO,CAAC,EAAE,mBAAmB,CAAC,wBAAwB,CAAC,GACxD,OAAO,CAAC,wBAAwB,CAAC;IAuBpC,SAAS,CAAC,UAAU;CAmBvB"}
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../../../src/core/provisioning/entities/policy/validator.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,SAAS,EAAgB,MAAM,SAAS,CAAC;AAKlD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D,qBAAa,2BAA4B,SAAQ,SAAS,CAAC,wBAAwB,CAAC;IACjE,GAAG,CACd,KAAK,CAAC,EAAE,cAAc,CAAC,wBAAwB,CAAC,EAChD,OAAO,CAAC,EAAE,mBAAmB,CAAC,wBAAwB,CAAC,GACxD,OAAO,CAAC,wBAAwB,CAAC;IAuBpC,SAAS,CAAC,UAAU;CAmBvB"}
@@ -4,9 +4,10 @@ import.meta.url;
4
4
  import { ProvisioningStrategyValidator } from "../../strategy/validator.mjs";
5
5
  import "../../strategy/index.mjs";
6
6
  import { createProvisioningEntitiesValidator } from "../utils.mjs";
7
+ import { defineIssueItem } from "../../../../node_modules/@ebec/core/dist/index.mjs";
7
8
  import { PolicyValidator } from "@authup/core-kit";
8
9
  import { BuiltInPolicyType } from "@authup/access";
9
- import { Container, ValidupError, defineIssueItem } from "validup";
10
+ import { Container, ValidupError } from "validup";
10
11
  import { createValidator } from "@validup/zod";
11
12
  import { z } from "zod";
12
13
  //#region src/core/provisioning/entities/policy/validator.ts
@@ -1 +1 @@
1
- {"version":3,"file":"validator.mjs","names":["BuiltInPolicyType","PolicyValidator","createValidator","Container","ValidupError","defineIssueItem","z","ProvisioningStrategyValidator","createProvisioningEntitiesValidator","PolicyProvisioningValidator","run","input","options","output","attributes","type","COMPOSITE","children","length","path","message","initialize","strategyValidator","mount","optional","attributesValidator","record","string","any","array","check"],"sources":["../../../../../src/core/provisioning/entities/policy/validator.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { BuiltInPolicyType } from '@authup/access';\nimport { PolicyValidator } from '@authup/core-kit';\nimport { createValidator } from '@validup/zod';\nimport type { ContainerInput, ContainerRunOptions } from 'validup';\nimport { Container, ValidupError, defineIssueItem } from 'validup';\nimport { z } from 'zod';\nimport { ProvisioningStrategyValidator } from '../../strategy/index.ts';\nimport { createProvisioningEntitiesValidator } from '../utils.ts';\nimport type { PolicyProvisioningEntity } from './types.ts';\n\nexport class PolicyProvisioningValidator extends Container<PolicyProvisioningEntity> {\n override async run(\n input?: ContainerInput<PolicyProvisioningEntity>,\n options?: ContainerRunOptions<PolicyProvisioningEntity>,\n ): Promise<PolicyProvisioningEntity> {\n const output = await super.run(input, options);\n\n // A composite policy with no children can never be satisfied and makes\n // any permission bound to it permanently un-grantable (#3304). Reject\n // it up front so provisioning fails at config load with a clear\n // message, rather than silently provisioning a \"stale\" permission that\n // only fails — with an opaque empty-issue error — once evaluated.\n if (\n output.attributes?.type === BuiltInPolicyType.COMPOSITE &&\n (!output.children || output.children.length === 0)\n ) {\n throw new ValidupError([\n defineIssueItem({\n path: ['children'],\n message: 'A composite policy must define at least one child policy.',\n }),\n ]);\n }\n\n return output;\n }\n\n protected initialize() {\n super.initialize();\n\n const strategyValidator = new ProvisioningStrategyValidator();\n this.mount('strategy', { optional: true }, strategyValidator);\n\n const attributesValidator = new PolicyValidator();\n this.mount('attributes', attributesValidator);\n\n this.mount('extraAttributes', { optional: true }, createValidator(\n z.record(z.string(), z.any()),\n ));\n\n this.mount('children', { optional: true }, createValidator(\n z\n .array(z.any())\n .check(createProvisioningEntitiesValidator(this)),\n ));\n }\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,IAAaS,8BAAb,cAAiDN,UAAAA;CAC7C,MAAeO,IACXC,OACAC,SACiC;EACjC,MAAMC,SAAS,MAAM,MAAMH,IAAIC,OAAOC,OAAAA;EAOtC,IACIC,OAAOC,YAAYC,SAASf,kBAAkBgB,cAC7C,CAACH,OAAOI,YAAYJ,OAAOI,SAASC,WAAW,IAEhD,MAAM,IAAId,aAAa,CACnBC,gBAAgB;GACZc,MAAM,CAAC,UAAW;GAClBC,SAAS;EACb,CAAA,CACH,CAAA;EAGL,OAAOP;CACX;CAEUQ,aAAa;EACnB,MAAMA,WAAAA;EAEN,MAAMC,oBAAoB,IAAIf,8BAAAA;EAC9B,KAAKgB,MAAM,YAAY,EAAEC,UAAU,KAAK,GAAGF,iBAAAA;EAE3C,MAAMG,sBAAsB,IAAIxB,gBAAAA;EAChC,KAAKsB,MAAM,cAAcE,mBAAAA;EAEzB,KAAKF,MAAM,mBAAmB,EAAEC,UAAU,KAAK,GAAGtB,gBAC9CI,EAAEoB,OAAOpB,EAAEqB,OAAM,GAAIrB,EAAEsB,IAAG,CAAA,CAAA,CAAA;EAG9B,KAAKL,MAAM,YAAY,EAAEC,UAAU,KAAK,GAAGtB,gBACvCI,EACKuB,MAAMvB,EAAEsB,IAAG,CAAA,CAAA,CACXE,MAAMtB,oCAAoC,IAAI,CAAA,CAAA,CAAA;CAE3D;AACJ"}
1
+ {"version":3,"file":"validator.mjs","names":["BuiltInPolicyType","PolicyValidator","createValidator","Container","ValidupError","defineIssueItem","z","ProvisioningStrategyValidator","createProvisioningEntitiesValidator","PolicyProvisioningValidator","run","input","options","output","attributes","type","COMPOSITE","children","length","path","message","initialize","strategyValidator","mount","optional","attributesValidator","record","string","any","array","check"],"sources":["../../../../../src/core/provisioning/entities/policy/validator.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { BuiltInPolicyType } from '@authup/access';\nimport { PolicyValidator } from '@authup/core-kit';\nimport { createValidator } from '@validup/zod';\nimport type { ContainerInput, ContainerRunOptions } from 'validup';\nimport { Container, ValidupError } from 'validup';\nimport { defineIssueItem } from '@ebec/core';\nimport { z } from 'zod';\nimport { ProvisioningStrategyValidator } from '../../strategy/index.ts';\nimport { createProvisioningEntitiesValidator } from '../utils.ts';\nimport type { PolicyProvisioningEntity } from './types.ts';\n\nexport class PolicyProvisioningValidator extends Container<PolicyProvisioningEntity> {\n override async run(\n input?: ContainerInput<PolicyProvisioningEntity>,\n options?: ContainerRunOptions<PolicyProvisioningEntity>,\n ): Promise<PolicyProvisioningEntity> {\n const output = await super.run(input, options);\n\n // A composite policy with no children can never be satisfied and makes\n // any permission bound to it permanently un-grantable (#3304). Reject\n // it up front so provisioning fails at config load with a clear\n // message, rather than silently provisioning a \"stale\" permission that\n // only fails — with an opaque empty-issue error — once evaluated.\n if (\n output.attributes?.type === BuiltInPolicyType.COMPOSITE &&\n (!output.children || output.children.length === 0)\n ) {\n throw new ValidupError([\n defineIssueItem({\n path: ['children'],\n message: 'A composite policy must define at least one child policy.',\n }),\n ]);\n }\n\n return output;\n }\n\n protected initialize() {\n super.initialize();\n\n const strategyValidator = new ProvisioningStrategyValidator();\n this.mount('strategy', { optional: true }, strategyValidator);\n\n const attributesValidator = new PolicyValidator();\n this.mount('attributes', attributesValidator);\n\n this.mount('extraAttributes', { optional: true }, createValidator(\n z.record(z.string(), z.any()),\n ));\n\n this.mount('children', { optional: true }, createValidator(\n z\n .array(z.any())\n .check(createProvisioningEntitiesValidator(this)),\n ));\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAkBA,IAAaS,8BAAb,cAAiDN,UAAAA;CAC7C,MAAeO,IACXC,OACAC,SACiC;EACjC,MAAMC,SAAS,MAAM,MAAMH,IAAIC,OAAOC,OAAAA;EAOtC,IACIC,OAAOC,YAAYC,SAASf,kBAAkBgB,cAC7C,CAACH,OAAOI,YAAYJ,OAAOI,SAASC,WAAW,IAEhD,MAAM,IAAId,aAAa,CACnBC,gBAAgB;GACZc,MAAM,CAAC,UAAW;GAClBC,SAAS;EACb,CAAA,CACH,CAAA;EAGL,OAAOP;CACX;CAEUQ,aAAa;EACnB,MAAMA,WAAAA;EAEN,MAAMC,oBAAoB,IAAIf,8BAAAA;EAC9B,KAAKgB,MAAM,YAAY,EAAEC,UAAU,KAAK,GAAGF,iBAAAA;EAE3C,MAAMG,sBAAsB,IAAIxB,gBAAAA;EAChC,KAAKsB,MAAM,cAAcE,mBAAAA;EAEzB,KAAKF,MAAM,mBAAmB,EAAEC,UAAU,KAAK,GAAGtB,gBAC9CI,EAAEoB,OAAOpB,EAAEqB,OAAM,GAAIrB,EAAEsB,IAAG,CAAA,CAAA,CAAA;EAG9B,KAAKL,MAAM,YAAY,EAAEC,UAAU,KAAK,GAAGtB,gBACvCI,EACKuB,MAAMvB,EAAEsB,IAAG,CAAA,CAAA,CACXE,MAAMtB,oCAAoC,IAAI,CAAA,CAAA,CAAA;CAE3D;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"wildcard-validator.d.ts","sourceRoot":"","sources":["../../../../../src/core/provisioning/entities/realm/wildcard-validator.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAS,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAiC,MAAM,SAAS,CAAC;AAInE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,qBAAa,kCAAmC,SAAQ,SAAS,CAAC,uBAAuB,CAAC;IACtF,SAAS,CAAC,UAAU;IA8BL,GAAG,CACd,KAAK,CAAC,EAAE,cAAc,CAAC,uBAAuB,CAAC,EAC/C,OAAO,CAAC,EAAE,mBAAmB,CAAC,uBAAuB,CAAC,GACvD,OAAO,CAAC,uBAAuB,CAAC;CAsBtC"}
1
+ {"version":3,"file":"wildcard-validator.d.ts","sourceRoot":"","sources":["../../../../../src/core/provisioning/entities/realm/wildcard-validator.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAEnE,OAAO,EAAE,SAAS,EAAgB,MAAM,SAAS,CAAC;AAKlD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,qBAAa,kCAAmC,SAAQ,SAAS,CAAC,uBAAuB,CAAC;IACtF,SAAS,CAAC,UAAU;IA8BL,GAAG,CACd,KAAK,CAAC,EAAE,cAAc,CAAC,uBAAuB,CAAC,EAC/C,OAAO,CAAC,EAAE,mBAAmB,CAAC,uBAAuB,CAAC,GACvD,OAAO,CAAC,uBAAuB,CAAC;CAsBtC"}
@@ -2,9 +2,10 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import "../../constants.mjs";
5
+ import { defineIssueItem } from "../../../../node_modules/@ebec/core/dist/index.mjs";
5
6
  import { RealmProvisioningRelationsValidator } from "./relations-validator.mjs";
6
7
  import { CLIENT_RESERVED_NAMES } from "@authup/core-kit";
7
- import { Container, ValidupError, defineIssueItem } from "validup";
8
+ import { Container, ValidupError } from "validup";
8
9
  import { createValidator } from "@validup/zod";
9
10
  import { z } from "zod";
10
11
  //#region src/core/provisioning/entities/realm/wildcard-validator.ts
@@ -1 +1 @@
1
- {"version":3,"file":"wildcard-validator.mjs","names":["CLIENT_RESERVED_NAMES","createValidator","Container","ValidupError","defineIssueItem","z","REALM_WILDCARD_NAME","RealmProvisioningRelationsValidator","RealmWildcardProvisioningValidator","initialize","mount","looseObject","name","literal","check","ctx","keys","Object","value","length","issues","push","code","input","message","optional","any","relationsValidator","run","options","output","clients","relations","forEach","client","index","attributes","includes","path","received"],"sources":["../../../../../src/core/provisioning/entities/realm/wildcard-validator.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { CLIENT_RESERVED_NAMES } from '@authup/core-kit';\nimport { createValidator } from '@validup/zod';\nimport type { ContainerInput, ContainerRunOptions, Issue } from 'validup';\nimport { Container, ValidupError, defineIssueItem } from 'validup';\nimport { z } from 'zod';\nimport { REALM_WILDCARD_NAME } from '../../constants.ts';\nimport { RealmProvisioningRelationsValidator } from './relations-validator.ts';\nimport type { RealmProvisioningEntity } from './types.ts';\n\n/**\n * Validates a WILDCARD realm entry (attributes.name === '*'). A wildcard\n * entry is a selector over realms, not a realm declaration, so it is\n * relations-only: `attributes` may carry nothing but the literal wildcard\n * name, and a realm-level `strategy` is rejected (`absent` would mean\n * \"delete every realm\"). Child strategies stay fully supported.\n *\n * Reserved client names (system / console clients) are rejected: a\n * template stamping one into every realm would fight the system client\n * MERGE on every boot.\n */\nexport class RealmWildcardProvisioningValidator extends Container<RealmProvisioningEntity> {\n protected initialize() {\n super.initialize();\n\n this.mount('attributes', createValidator(\n z.looseObject({ name: z.literal(REALM_WILDCARD_NAME) }).check((ctx) => {\n const keys = Object.keys(ctx.value);\n if (keys.length > 1) {\n ctx.issues.push({\n code: 'custom',\n input: ctx.value,\n message: 'A wildcard realm entry is relations-only and can not declare realm attributes.',\n });\n }\n }),\n ));\n\n this.mount('strategy', { optional: true }, createValidator(\n z.any().check((ctx) => {\n ctx.issues.push({\n code: 'custom',\n input: ctx.value,\n message: 'A wildcard realm entry can not declare a realm-level strategy.',\n });\n }),\n ));\n\n const relationsValidator = new RealmProvisioningRelationsValidator();\n this.mount('relations', { optional: true }, relationsValidator);\n }\n\n override async run(\n input?: ContainerInput<RealmProvisioningEntity>,\n options?: ContainerRunOptions<RealmProvisioningEntity>,\n ): Promise<RealmProvisioningEntity> {\n const output = await super.run(input, options);\n\n const issues : Issue[] = [];\n const clients = output.relations?.clients ?? [];\n clients.forEach((client, index) => {\n const name = client.attributes?.name;\n if (name && (CLIENT_RESERVED_NAMES as string[]).includes(name)) {\n issues.push(defineIssueItem({\n path: ['relations', 'clients', index, 'attributes', 'name'],\n message: `The client name \"${name}\" is reserved for system clients and can not be declared in a wildcard realm entry.`,\n received: name,\n }));\n }\n });\n\n if (issues.length > 0) {\n throw new ValidupError(issues);\n }\n\n return output;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;GA2BA,IAAaQ,qCAAb,cAAwDN,UAAAA;CAC1CO,aAAa;EACnB,MAAMA,WAAAA;EAEN,KAAKC,MAAM,cAAcT,gBACrBI,EAAEM,YAAY,EAAEC,MAAMP,EAAEQ,QAAAA,GAAQP,EAAqB,CAAA,CAAA,CAAGQ,OAAOC,QAAAA;GAE3D,IADaE,OAAOD,KAAKD,IAAIG,KACzBF,CAAAA,CAAKG,SAAS,GACdJ,IAAIK,OAAOC,KAAK;IACZC,MAAM;IACNC,OAAOR,IAAIG;IACXM,SAAS;GACb,CAAA;EAER,CAAA,CAAA,CAAA;EAGJ,KAAKd,MAAM,YAAY,EAAEe,UAAU,KAAK,GAAGxB,gBACvCI,EAAEqB,IAAG,CAAA,CAAGZ,OAAOC,QAAAA;GACXA,IAAIK,OAAOC,KAAK;IACZC,MAAM;IACNC,OAAOR,IAAIG;IACXM,SAAS;GACb,CAAA;EACJ,CAAA,CAAA,CAAA;EAGJ,MAAMG,qBAAqB,IAAIpB,oCAAAA;EAC/B,KAAKG,MAAM,aAAa,EAAEe,UAAU,KAAK,GAAGE,kBAAAA;CAChD;CAEA,MAAeC,IACXL,OACAM,SACgC;EAChC,MAAMC,SAAS,MAAM,MAAMF,IAAIL,OAAOM,OAAAA;EAEtC,MAAMT,SAAmB,CAAA;EAEzBW,CADgBD,OAAOE,WAAWD,WAAW,CAAA,EAAA,CACrCE,SAASC,QAAQC,UAAAA;GACrB,MAAMvB,OAAOsB,OAAOE,YAAYxB;GAChC,IAAIA,QAAQ,sBAAoCyB,SAASzB,IAAAA,GACrDQ,OAAOC,KAAKjB,gBAAgB;IACxBkC,MAAM;KAAC;KAAa;KAAWH;KAAO;KAAc;IAAO;IAC3DX,SAAS,oBAAoBZ,KAAK;IAClC2B,UAAU3B;GACd,CAAA,CAAA;EAER,CAAA;EAEA,IAAIQ,OAAOD,SAAS,GAChB,MAAM,IAAIhB,aAAaiB,MAAAA;EAG3B,OAAOU;CACX;AACJ"}
1
+ {"version":3,"file":"wildcard-validator.mjs","names":["CLIENT_RESERVED_NAMES","createValidator","Container","ValidupError","defineIssueItem","z","REALM_WILDCARD_NAME","RealmProvisioningRelationsValidator","RealmWildcardProvisioningValidator","initialize","mount","looseObject","name","literal","check","ctx","keys","Object","value","length","issues","push","code","input","message","optional","any","relationsValidator","run","options","output","clients","relations","forEach","client","index","attributes","includes","path","received"],"sources":["../../../../../src/core/provisioning/entities/realm/wildcard-validator.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { CLIENT_RESERVED_NAMES } from '@authup/core-kit';\nimport { createValidator } from '@validup/zod';\nimport type { ContainerInput, ContainerRunOptions } from 'validup';\nimport type { Issue } from '@ebec/core';\nimport { Container, ValidupError } from 'validup';\nimport { defineIssueItem } from '@ebec/core';\nimport { z } from 'zod';\nimport { REALM_WILDCARD_NAME } from '../../constants.ts';\nimport { RealmProvisioningRelationsValidator } from './relations-validator.ts';\nimport type { RealmProvisioningEntity } from './types.ts';\n\n/**\n * Validates a WILDCARD realm entry (attributes.name === '*'). A wildcard\n * entry is a selector over realms, not a realm declaration, so it is\n * relations-only: `attributes` may carry nothing but the literal wildcard\n * name, and a realm-level `strategy` is rejected (`absent` would mean\n * \"delete every realm\"). Child strategies stay fully supported.\n *\n * Reserved client names (system / console clients) are rejected: a\n * template stamping one into every realm would fight the system client\n * MERGE on every boot.\n */\nexport class RealmWildcardProvisioningValidator extends Container<RealmProvisioningEntity> {\n protected initialize() {\n super.initialize();\n\n this.mount('attributes', createValidator(\n z.looseObject({ name: z.literal(REALM_WILDCARD_NAME) }).check((ctx) => {\n const keys = Object.keys(ctx.value);\n if (keys.length > 1) {\n ctx.issues.push({\n code: 'custom',\n input: ctx.value,\n message: 'A wildcard realm entry is relations-only and can not declare realm attributes.',\n });\n }\n }),\n ));\n\n this.mount('strategy', { optional: true }, createValidator(\n z.any().check((ctx) => {\n ctx.issues.push({\n code: 'custom',\n input: ctx.value,\n message: 'A wildcard realm entry can not declare a realm-level strategy.',\n });\n }),\n ));\n\n const relationsValidator = new RealmProvisioningRelationsValidator();\n this.mount('relations', { optional: true }, relationsValidator);\n }\n\n override async run(\n input?: ContainerInput<RealmProvisioningEntity>,\n options?: ContainerRunOptions<RealmProvisioningEntity>,\n ): Promise<RealmProvisioningEntity> {\n const output = await super.run(input, options);\n\n const issues : Issue[] = [];\n const clients = output.relations?.clients ?? [];\n clients.forEach((client, index) => {\n const name = client.attributes?.name;\n if (name && (CLIENT_RESERVED_NAMES as string[]).includes(name)) {\n issues.push(defineIssueItem({\n path: ['relations', 'clients', index, 'attributes', 'name'],\n message: `The client name \"${name}\" is reserved for system clients and can not be declared in a wildcard realm entry.`,\n received: name,\n }));\n }\n });\n\n if (issues.length > 0) {\n throw new ValidupError(issues);\n }\n\n return output;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;GA6BA,IAAaQ,qCAAb,cAAwDN,UAAAA;CAC1CO,aAAa;EACnB,MAAMA,WAAAA;EAEN,KAAKC,MAAM,cAAcT,gBACrBI,EAAEM,YAAY,EAAEC,MAAMP,EAAEQ,QAAAA,GAAQP,EAAqB,CAAA,CAAA,CAAGQ,OAAOC,QAAAA;GAE3D,IADaE,OAAOD,KAAKD,IAAIG,KACzBF,CAAAA,CAAKG,SAAS,GACdJ,IAAIK,OAAOC,KAAK;IACZC,MAAM;IACNC,OAAOR,IAAIG;IACXM,SAAS;GACb,CAAA;EAER,CAAA,CAAA,CAAA;EAGJ,KAAKd,MAAM,YAAY,EAAEe,UAAU,KAAK,GAAGxB,gBACvCI,EAAEqB,IAAG,CAAA,CAAGZ,OAAOC,QAAAA;GACXA,IAAIK,OAAOC,KAAK;IACZC,MAAM;IACNC,OAAOR,IAAIG;IACXM,SAAS;GACb,CAAA;EACJ,CAAA,CAAA,CAAA;EAGJ,MAAMG,qBAAqB,IAAIpB,oCAAAA;EAC/B,KAAKG,MAAM,aAAa,EAAEe,UAAU,KAAK,GAAGE,kBAAAA;CAChD;CAEA,MAAeC,IACXL,OACAM,SACgC;EAChC,MAAMC,SAAS,MAAM,MAAMF,IAAIL,OAAOM,OAAAA;EAEtC,MAAMT,SAAmB,CAAA;EAEzBW,CADgBD,OAAOE,WAAWD,WAAW,CAAA,EAAA,CACrCE,SAASC,QAAQC,UAAAA;GACrB,MAAMvB,OAAOsB,OAAOE,YAAYxB;GAChC,IAAIA,QAAQ,sBAAoCyB,SAASzB,IAAAA,GACrDQ,OAAOC,KAAKjB,gBAAgB;IACxBkC,MAAM;KAAC;KAAa;KAAWH;KAAO;KAAc;IAAO;IAC3DX,SAAS,oBAAoBZ,KAAK;IAClC2B,UAAU3B;GACd,CAAA,CAAA;EAER,CAAA;EAEA,IAAIQ,OAAOD,SAAS,GAChB,MAAM,IAAIhB,aAAaiB,MAAAA;EAG3B,OAAOU;CACX;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"evaluator.mjs","names":["and","or","BuiltInPolicyType","IdentityPolicyEvaluator","PermissionBindingPolicyValidator","PolicyEngine","PolicyIssueCode","RealmMatchPolicyEvaluator","RealmScope","aggregatePermissionPolicyBindings","definePolicyIssueItem","maybeInvertPolicyOutcome","normalizeRealmScope","PermissionBindingPolicyEvaluator","validator","identityEvaluator","realmMatchEvaluator","identityPermissionProvider","requires","PERMISSION_BINDING","accessData","ctx","data","has","isValidated","get","set","setValidated","evaluate","value","policy","run","identity","success","issues","code","DATA_MISSING","message","path","binding","identityBindings","getFor","then","bindings","filter","item","permission","name","realmId","clientId","length","invert","aggr","pendingIssues","conditions","pending","lowerable","grant","grants","reachCondition","realmOutcome","scope","realmScope","NONE","ANY","condition","push","engine","evaluators","outcome","withConditions","result"],"sources":["../../../../src/core/security/policy/evaluator.ts"],"sourcesContent":["/*\n * Copyright (c) 2024-2024.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { ICondition } from '@rapiq/core';\nimport { and, or } from '@rapiq/core';\nimport type { Issue } from 'validup';\nimport type {\n IPolicyEvaluator,\n PermissionPolicyBindingAggregated,\n PolicyEvaluationContext,\n PolicyEvaluationResult,\n} from '@authup/access';\nimport {\n BuiltInPolicyType,\n IdentityPolicyEvaluator,\n PermissionBindingPolicyValidator,\n PolicyEngine,\n PolicyIssueCode,\n RealmMatchPolicyEvaluator,\n RealmScope,\n aggregatePermissionPolicyBindings,\n definePolicyIssueItem,\n maybeInvertPolicyOutcome,\n normalizeRealmScope,\n} from '@authup/access';\nimport type { IIdentityPermissionProvider } from '../../identity/permission/types.ts';\n\nexport class PermissionBindingPolicyEvaluator implements IPolicyEvaluator {\n protected validator : PermissionBindingPolicyValidator;\n\n protected identityEvaluator: IdentityPolicyEvaluator;\n\n protected realmMatchEvaluator : RealmMatchPolicyEvaluator;\n\n protected identityPermissionProvider: IIdentityPermissionProvider;\n\n constructor(identityPermissionProvider: IIdentityPermissionProvider) {\n this.validator = new PermissionBindingPolicyValidator();\n this.identityEvaluator = new IdentityPolicyEvaluator();\n this.realmMatchEvaluator = new RealmMatchPolicyEvaluator();\n this.identityPermissionProvider = identityPermissionProvider;\n }\n\n requires() : string[] {\n // IDENTITY is deliberately NOT declared: a missing identity must stay a\n // settled DATA_MISSING deny (fail-closed) so a scope-restricted or anonymous\n // bearer still fails the pre-gate through system.default — pending would be\n // permitted there.\n return [BuiltInPolicyType.PERMISSION_BINDING];\n }\n\n async accessData(ctx: PolicyEvaluationContext) : Promise<PermissionPolicyBindingAggregated | null> {\n if (!ctx.data.has(BuiltInPolicyType.PERMISSION_BINDING)) {\n return null;\n }\n\n if (ctx.data.isValidated(BuiltInPolicyType.PERMISSION_BINDING)) {\n return ctx.data.get(BuiltInPolicyType.PERMISSION_BINDING);\n }\n\n const data = ctx.data.get<PermissionPolicyBindingAggregated>(BuiltInPolicyType.PERMISSION_BINDING);\n\n ctx.data.set(BuiltInPolicyType.PERMISSION_BINDING, data);\n ctx.data.setValidated(BuiltInPolicyType.PERMISSION_BINDING);\n\n return data;\n }\n\n async evaluate(value: Record<string, any>, ctx: PolicyEvaluationContext): Promise<PolicyEvaluationResult> {\n const policy = await this.validator.run(value);\n\n const identity = await this.identityEvaluator.accessData(ctx);\n if (!identity) {\n return {\n success: false,\n issues: [\n definePolicyIssueItem({\n code: PolicyIssueCode.DATA_MISSING,\n message: 'The data property identity is missing',\n path: ctx.path,\n }),\n ],\n };\n }\n const binding = await this.accessData(ctx);\n if (!binding) {\n return {\n success: false,\n issues: [\n definePolicyIssueItem({\n code: PolicyIssueCode.DATA_MISSING,\n message: 'The data property permission is missing',\n path: ctx.path,\n }),\n ],\n };\n }\n\n const identityBindings = await this.identityPermissionProvider.getFor(identity)\n .then((bindings) => bindings.filter((item) => {\n if (item.permission.name !== binding.permission.name) {\n return false;\n }\n\n return (binding.permission.realmId ?? null) === (item.permission.realmId ?? null) &&\n (binding.permission.clientId ?? null) === (item.permission.clientId ?? null);\n }));\n\n if (identityBindings.length === 0) {\n return { success: maybeInvertPolicyOutcome(false, policy.invert) };\n }\n\n const [aggr] = aggregatePermissionPolicyBindings(identityBindings);\n if (!aggr) {\n return { success: maybeInvertPolicyOutcome(false, policy.invert) };\n }\n\n // identityBindings is filtered to a single (name, realm_id, client_id) key, so the\n // aggregate yields exactly one entry whose `grants` are the actor's per-grant\n // (realm_scope, policy) disjunction terms; access is the DISJUNCTION over them:\n // ∃ grant . realmScopeMatches(grant.realmScope, resource) ∧ (grant.policy passes)\n // Pairing each grant's realm reach with its OWN policy is what fixes both the mixed\n // policy-free + policy-bound UNDER-grant (a policy-free `own` grant must not mask a\n // policy-bound `any` grant's wider reach) and the symmetric OVER-grant (an `own`\n // grant's passing policy must not ride an `any` grant's wider reach).\n const issues : Issue[] = [];\n const pendingIssues : Issue[] = [];\n const conditions : ICondition[] = [];\n let pending = false;\n let lowerable = true;\n for (const grant of aggr.grants) {\n // Realm reach (coarse, actor-relative) — a SEPARATE factor from the grant's\n // policy, ANDed with it. The realm-match evaluator runs in SCOPE MODE: it reads the\n // resource realm from ctx.data[REALM_MATCH] (fallback ATTRIBUTES.realmId) and\n // neutral-passes when absent (preEvaluate / gate checks / realm-less resources) —\n // key-PRESENCE is the discriminator. Invoked DIRECTLY (not via PolicyEngine) so\n // the reach factor can never be skipped by a caller's include/exclude filters or\n // deferred by the engine's data-availability gate.\n //\n // Under `withConditions` (query-build) with no resource realm present, the\n // reach PENDS with its condition form over the row's realm column instead of\n // neutral-passing — `any` stays unrestricted and `none` reaches nothing, so\n // neither pollutes the composed WHERE with constant terms.\n let reachCondition : ICondition | null = null;\n const realmOutcome = await this.realmMatchEvaluator.evaluate(\n { scope: grant.realmScope },\n ctx,\n );\n if (realmOutcome.pending) {\n const scope = normalizeRealmScope(grant.realmScope);\n if (scope === RealmScope.NONE) {\n continue;\n }\n\n if (scope !== RealmScope.ANY) {\n if (!realmOutcome.condition) {\n pending = true;\n lowerable = false;\n continue;\n }\n\n reachCondition = realmOutcome.condition;\n }\n } else if (!realmOutcome.success) {\n continue;\n }\n\n if (!grant.policy) {\n if (!reachCondition) {\n // Reach matches and this grant carries no further restriction.\n return { success: maybeInvertPolicyOutcome(true, policy.invert) };\n }\n\n // Reach is the grant's only restriction — a pure condition term.\n conditions.push(reachCondition);\n pending = true;\n continue;\n }\n\n // Missing evaluators is a misconfiguration: let the engine fail CLOSED with a\n // surfaced POLICY_EVALUATOR_NOT_FOUND issue (new PolicyEngine(undefined) defaults\n // to an empty registry, so grant.policy resolves to no evaluator) rather than\n // swallowing the grant with a silent issue-less deny.\n const engine = new PolicyEngine(ctx.evaluators);\n const outcome = await engine.evaluate(grant.policy, {\n ...ctx,\n path: [\n ...(ctx.path || []),\n ],\n });\n\n if (outcome.success) {\n if (!reachCondition) {\n return { success: maybeInvertPolicyOutcome(true, policy.invert) };\n }\n\n conditions.push(reachCondition);\n pending = true;\n continue;\n }\n\n // A PENDING junction policy (required data key absent — e.g. an ATTRIBUTES\n // policy at the pre-gate) leaves this grant term UNKNOWN, not failed: the\n // grant could still pass once the data arrives, so it must not settle the\n // disjunction to false.\n if (outcome.pending) {\n pending = true;\n\n if (ctx.withConditions && outcome.condition) {\n conditions.push(\n reachCondition ?\n and(reachCondition, outcome.condition) :\n outcome.condition,\n );\n } else {\n lowerable = false;\n }\n\n if (outcome.issues) {\n pendingIssues.push(...outcome.issues);\n }\n\n continue;\n }\n\n if (outcome.issues) {\n issues.push(...outcome.issues);\n }\n }\n\n // No grant term settled true. If some term is still pending, the disjunction is\n // pending — deliberately uninverted (unknown stays unknown under negation). The\n // condition form composes the grant terms as a disjunction, all-or-nothing: a\n // single non-expressible grant term makes the whole binding non-expressible\n // (pushing a partial OR would wrongly exclude rows).\n if (pending) {\n const result : PolicyEvaluationResult = {\n success: false,\n pending: true,\n issues: pendingIssues,\n };\n\n if (ctx.withConditions && lowerable && conditions.length > 0) {\n result.condition = conditions.length === 1 ?\n conditions[0]! :\n or(...conditions);\n }\n\n return result;\n }\n\n // No grant term satisfied (reach ∧ policies). Apply `invert` ONCE to the aggregated\n // disjunction outcome — never per term, which would corrupt the quantifier.\n const success = maybeInvertPolicyOutcome(false, policy.invert);\n return {\n success,\n issues: success ? [] : issues,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"evaluator.mjs","names":["and","or","BuiltInPolicyType","IdentityPolicyEvaluator","PermissionBindingPolicyValidator","PolicyEngine","PolicyIssueCode","RealmMatchPolicyEvaluator","RealmScope","aggregatePermissionPolicyBindings","definePolicyIssueItem","maybeInvertPolicyOutcome","normalizeRealmScope","PermissionBindingPolicyEvaluator","validator","identityEvaluator","realmMatchEvaluator","identityPermissionProvider","requires","PERMISSION_BINDING","accessData","ctx","data","has","isValidated","get","set","setValidated","evaluate","value","policy","run","identity","success","issues","code","DATA_MISSING","message","path","binding","identityBindings","getFor","then","bindings","filter","item","permission","name","realmId","clientId","length","invert","aggr","pendingIssues","conditions","pending","lowerable","grant","grants","reachCondition","realmOutcome","scope","realmScope","NONE","ANY","condition","push","engine","evaluators","outcome","withConditions","result"],"sources":["../../../../src/core/security/policy/evaluator.ts"],"sourcesContent":["/*\n * Copyright (c) 2024-2024.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { ICondition } from '@rapiq/core';\nimport { and, or } from '@rapiq/core';\nimport type { Issue } from '@ebec/core';\nimport type {\n IPolicyEvaluator,\n PermissionPolicyBindingAggregated,\n PolicyEvaluationContext,\n PolicyEvaluationResult,\n} from '@authup/access';\nimport {\n BuiltInPolicyType,\n IdentityPolicyEvaluator,\n PermissionBindingPolicyValidator,\n PolicyEngine,\n PolicyIssueCode,\n RealmMatchPolicyEvaluator,\n RealmScope,\n aggregatePermissionPolicyBindings,\n definePolicyIssueItem,\n maybeInvertPolicyOutcome,\n normalizeRealmScope,\n} from '@authup/access';\nimport type { IIdentityPermissionProvider } from '../../identity/permission/types.ts';\n\nexport class PermissionBindingPolicyEvaluator implements IPolicyEvaluator {\n protected validator : PermissionBindingPolicyValidator;\n\n protected identityEvaluator: IdentityPolicyEvaluator;\n\n protected realmMatchEvaluator : RealmMatchPolicyEvaluator;\n\n protected identityPermissionProvider: IIdentityPermissionProvider;\n\n constructor(identityPermissionProvider: IIdentityPermissionProvider) {\n this.validator = new PermissionBindingPolicyValidator();\n this.identityEvaluator = new IdentityPolicyEvaluator();\n this.realmMatchEvaluator = new RealmMatchPolicyEvaluator();\n this.identityPermissionProvider = identityPermissionProvider;\n }\n\n requires() : string[] {\n // IDENTITY is deliberately NOT declared: a missing identity must stay a\n // settled DATA_MISSING deny (fail-closed) so a scope-restricted or anonymous\n // bearer still fails the pre-gate through system.default — pending would be\n // permitted there.\n return [BuiltInPolicyType.PERMISSION_BINDING];\n }\n\n async accessData(ctx: PolicyEvaluationContext) : Promise<PermissionPolicyBindingAggregated | null> {\n if (!ctx.data.has(BuiltInPolicyType.PERMISSION_BINDING)) {\n return null;\n }\n\n if (ctx.data.isValidated(BuiltInPolicyType.PERMISSION_BINDING)) {\n return ctx.data.get(BuiltInPolicyType.PERMISSION_BINDING);\n }\n\n const data = ctx.data.get<PermissionPolicyBindingAggregated>(BuiltInPolicyType.PERMISSION_BINDING);\n\n ctx.data.set(BuiltInPolicyType.PERMISSION_BINDING, data);\n ctx.data.setValidated(BuiltInPolicyType.PERMISSION_BINDING);\n\n return data;\n }\n\n async evaluate(value: Record<string, any>, ctx: PolicyEvaluationContext): Promise<PolicyEvaluationResult> {\n const policy = await this.validator.run(value);\n\n const identity = await this.identityEvaluator.accessData(ctx);\n if (!identity) {\n return {\n success: false,\n issues: [\n definePolicyIssueItem({\n code: PolicyIssueCode.DATA_MISSING,\n message: 'The data property identity is missing',\n path: ctx.path,\n }),\n ],\n };\n }\n const binding = await this.accessData(ctx);\n if (!binding) {\n return {\n success: false,\n issues: [\n definePolicyIssueItem({\n code: PolicyIssueCode.DATA_MISSING,\n message: 'The data property permission is missing',\n path: ctx.path,\n }),\n ],\n };\n }\n\n const identityBindings = await this.identityPermissionProvider.getFor(identity)\n .then((bindings) => bindings.filter((item) => {\n if (item.permission.name !== binding.permission.name) {\n return false;\n }\n\n return (binding.permission.realmId ?? null) === (item.permission.realmId ?? null) &&\n (binding.permission.clientId ?? null) === (item.permission.clientId ?? null);\n }));\n\n if (identityBindings.length === 0) {\n return { success: maybeInvertPolicyOutcome(false, policy.invert) };\n }\n\n const [aggr] = aggregatePermissionPolicyBindings(identityBindings);\n if (!aggr) {\n return { success: maybeInvertPolicyOutcome(false, policy.invert) };\n }\n\n // identityBindings is filtered to a single (name, realm_id, client_id) key, so the\n // aggregate yields exactly one entry whose `grants` are the actor's per-grant\n // (realm_scope, policy) disjunction terms; access is the DISJUNCTION over them:\n // ∃ grant . realmScopeMatches(grant.realmScope, resource) ∧ (grant.policy passes)\n // Pairing each grant's realm reach with its OWN policy is what fixes both the mixed\n // policy-free + policy-bound UNDER-grant (a policy-free `own` grant must not mask a\n // policy-bound `any` grant's wider reach) and the symmetric OVER-grant (an `own`\n // grant's passing policy must not ride an `any` grant's wider reach).\n const issues : Issue[] = [];\n const pendingIssues : Issue[] = [];\n const conditions : ICondition[] = [];\n let pending = false;\n let lowerable = true;\n for (const grant of aggr.grants) {\n // Realm reach (coarse, actor-relative) — a SEPARATE factor from the grant's\n // policy, ANDed with it. The realm-match evaluator runs in SCOPE MODE: it reads the\n // resource realm from ctx.data[REALM_MATCH] (fallback ATTRIBUTES.realmId) and\n // neutral-passes when absent (preEvaluate / gate checks / realm-less resources) —\n // key-PRESENCE is the discriminator. Invoked DIRECTLY (not via PolicyEngine) so\n // the reach factor can never be skipped by a caller's include/exclude filters or\n // deferred by the engine's data-availability gate.\n //\n // Under `withConditions` (query-build) with no resource realm present, the\n // reach PENDS with its condition form over the row's realm column instead of\n // neutral-passing — `any` stays unrestricted and `none` reaches nothing, so\n // neither pollutes the composed WHERE with constant terms.\n let reachCondition : ICondition | null = null;\n const realmOutcome = await this.realmMatchEvaluator.evaluate(\n { scope: grant.realmScope },\n ctx,\n );\n if (realmOutcome.pending) {\n const scope = normalizeRealmScope(grant.realmScope);\n if (scope === RealmScope.NONE) {\n continue;\n }\n\n if (scope !== RealmScope.ANY) {\n if (!realmOutcome.condition) {\n pending = true;\n lowerable = false;\n continue;\n }\n\n reachCondition = realmOutcome.condition;\n }\n } else if (!realmOutcome.success) {\n continue;\n }\n\n if (!grant.policy) {\n if (!reachCondition) {\n // Reach matches and this grant carries no further restriction.\n return { success: maybeInvertPolicyOutcome(true, policy.invert) };\n }\n\n // Reach is the grant's only restriction — a pure condition term.\n conditions.push(reachCondition);\n pending = true;\n continue;\n }\n\n // Missing evaluators is a misconfiguration: let the engine fail CLOSED with a\n // surfaced POLICY_EVALUATOR_NOT_FOUND issue (new PolicyEngine(undefined) defaults\n // to an empty registry, so grant.policy resolves to no evaluator) rather than\n // swallowing the grant with a silent issue-less deny.\n const engine = new PolicyEngine(ctx.evaluators);\n const outcome = await engine.evaluate(grant.policy, {\n ...ctx,\n path: [\n ...(ctx.path || []),\n ],\n });\n\n if (outcome.success) {\n if (!reachCondition) {\n return { success: maybeInvertPolicyOutcome(true, policy.invert) };\n }\n\n conditions.push(reachCondition);\n pending = true;\n continue;\n }\n\n // A PENDING junction policy (required data key absent — e.g. an ATTRIBUTES\n // policy at the pre-gate) leaves this grant term UNKNOWN, not failed: the\n // grant could still pass once the data arrives, so it must not settle the\n // disjunction to false.\n if (outcome.pending) {\n pending = true;\n\n if (ctx.withConditions && outcome.condition) {\n conditions.push(\n reachCondition ?\n and(reachCondition, outcome.condition) :\n outcome.condition,\n );\n } else {\n lowerable = false;\n }\n\n if (outcome.issues) {\n pendingIssues.push(...outcome.issues);\n }\n\n continue;\n }\n\n if (outcome.issues) {\n issues.push(...outcome.issues);\n }\n }\n\n // No grant term settled true. If some term is still pending, the disjunction is\n // pending — deliberately uninverted (unknown stays unknown under negation). The\n // condition form composes the grant terms as a disjunction, all-or-nothing: a\n // single non-expressible grant term makes the whole binding non-expressible\n // (pushing a partial OR would wrongly exclude rows).\n if (pending) {\n const result : PolicyEvaluationResult = {\n success: false,\n pending: true,\n issues: pendingIssues,\n };\n\n if (ctx.withConditions && lowerable && conditions.length > 0) {\n result.condition = conditions.length === 1 ?\n conditions[0]! :\n or(...conditions);\n }\n\n return result;\n }\n\n // No grant term satisfied (reach ∧ policies). Apply `invert` ONCE to the aggregated\n // disjunction outcome — never per term, which would corrupt the quantifier.\n const success = maybeInvertPolicyOutcome(false, policy.invert);\n return {\n success,\n issues: success ? [] : issues,\n };\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,130 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region ../../node_modules/@ebec/core/dist/index.mjs
5
+ /**
6
+ * Vocabulary of well-known issue codes. Validators map their own failure
7
+ * kinds onto these so consumer-side translation catalogs can ship one
8
+ * localized message per code instead of falling back to a generic
9
+ * "invalid value".
10
+ *
11
+ * Tracks the common ground between vuelidate, zod, joi, yup and
12
+ * validator.js. The list lifts from vuelidate's catalog (snake_cased);
13
+ * modern format codes (`PATTERN`, `UUID`, `DATE`, `JSON`, `BASE64`,
14
+ * `STRONG_PASSWORD`, …) cover ground vuelidate predates. Every format
15
+ * code names the format positively (`UUID`, `EMAIL`, `URL`, …) rather
16
+ * than the failure mode (`NOT_UUID`, `INVALID_EMAIL`) so the registry
17
+ * reads consistently.
18
+ *
19
+ * **This vocabulary is a default, not a requirement.** `IssueItem.code`
20
+ * is widened to `IssueCode | (string & {})`, so a library with its own
21
+ * vocabulary — `rapiq`'s `key_not_allowed`, an app's `email_taken` —
22
+ * emits well-formed issues without touching this const at all. If you
23
+ * want a typed const for your own codes, define one alongside this one:
24
+ *
25
+ * ```ts
26
+ * import { IssueCode } from '@ebec/core';
27
+ *
28
+ * export const AppCode = {
29
+ * ...IssueCode,
30
+ * EMAIL_TAKEN: 'email_taken',
31
+ * } as const;
32
+ * ```
33
+ *
34
+ * Each entry's JSDoc documents the structured `data` producers should
35
+ * attach when constructing the issue — templates can rely on those
36
+ * placeholders being present (`{min}`, `{max}`, `{other}`, etc.).
37
+ */
38
+ const IssueCode = {
39
+ /** Generic fallback when no more-specific code applies. `data`: — */
40
+ VALUE_INVALID: "value_invalid",
41
+ /** Every branch of a `oneOf` alternative failed. `data`: — */
42
+ ONE_OF_FAILED: "one_of_failed",
43
+ /** Value is missing, `undefined`, `null`, or empty per the validator's semantics. `data`: — */
44
+ REQUIRED: "required",
45
+ /** Value contains characters outside the alphabetical set. `data`: — */
46
+ ALPHA: "alpha",
47
+ /** Value contains characters outside the alphanumeric set. `data`: — */
48
+ ALPHA_NUM: "alpha_num",
49
+ /** Value is not a number. `data`: — */
50
+ NUMERIC: "numeric",
51
+ /** Value is not an integer. `data`: — */
52
+ INTEGER: "integer",
53
+ /** Value is not a decimal number. `data`: — */
54
+ DECIMAL: "decimal",
55
+ /** Value is shorter than the configured minimum. `data`: `{ min: number }` */
56
+ MIN_LENGTH: "min_length",
57
+ /** Value is longer than the configured maximum. `data`: `{ max: number }` */
58
+ MAX_LENGTH: "max_length",
59
+ /** Numeric value is below the configured minimum. `data`: `{ min: number }` */
60
+ MIN_VALUE: "min_value",
61
+ /** Numeric value is above the configured maximum. `data`: `{ max: number }` */
62
+ MAX_VALUE: "max_value",
63
+ /** Numeric value falls outside the configured `[min, max]` range. `data`: `{ min: number, max: number }` */
64
+ BETWEEN: "between",
65
+ /** Value is not a valid email address. `data`: — */
66
+ EMAIL: "email",
67
+ /** Value is not a valid URL. `data`: — */
68
+ URL: "url",
69
+ /** Value is not a valid IP address. `data`: — */
70
+ IP_ADDRESS: "ip_address",
71
+ /** Value is not a valid MAC address. `data`: — */
72
+ MAC_ADDRESS: "mac_address",
73
+ /** Value is not a valid UUID. `data`: — */
74
+ UUID: "uuid",
75
+ /** Value is not a valid date / cannot be parsed as a date. `data`: — */
76
+ DATE: "date",
77
+ /**
78
+ * Value does not match the expected regex pattern. `data`:
79
+ * `{ pattern: string }` — source of the regex without flags so
80
+ * catalogs can quote it in human-readable form.
81
+ */
82
+ PATTERN: "pattern",
83
+ /** Value is not valid JSON. `data`: — */
84
+ JSON: "json",
85
+ /** Value is not valid base64. `data`: — */
86
+ BASE64: "base64",
87
+ /**
88
+ * Value does not meet the configured strong-password rules. `data`:
89
+ * `{ minLength?: number, minLowercase?: number, minUppercase?: number,
90
+ * minNumbers?: number, minSymbols?: number }` — only the keys the
91
+ * producer chose to surface; missing keys mean "the default for that
92
+ * setting wasn't configured."
93
+ */
94
+ STRONG_PASSWORD: "strong_password",
95
+ /**
96
+ * Value must equal another named field's value (sibling-field assertion,
97
+ * e.g. password-confirm). `data`: `{ other: string }` — the name of
98
+ * the field being compared against.
99
+ */
100
+ SAME_AS: "same_as"
101
+ };
102
+ /**
103
+ * Build an `IssueItem`. TS uses the supplied `code` to pick the right
104
+ * `data` requirement at the call site — passing `MIN_LENGTH` without
105
+ * `data: { min }` is a compile error; passing `STRONG_PASSWORD` with
106
+ * `data: { pointsPerUnique: 5 }` is a compile error.
107
+ *
108
+ * `code` may be omitted (defaults to `IssueCode.VALUE_INVALID` — bare,
109
+ * no data).
110
+ *
111
+ * ```ts
112
+ * defineIssueItem({
113
+ * code: IssueCode.MIN_LENGTH,
114
+ * path: ['name'],
115
+ * message: 'Must be at least 3 characters',
116
+ * data: { min: 3 },
117
+ * });
118
+ * ```
119
+ */
120
+ function defineIssueItem(data) {
121
+ return {
122
+ type: "item",
123
+ ...data,
124
+ code: data.code || IssueCode.VALUE_INVALID
125
+ };
126
+ }
127
+ //#endregion
128
+ export { IssueCode, defineIssueItem };
129
+
130
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../node_modules/@ebec/core/dist/index.mjs"],"sourcesContent":["//#region src/helpers/object.ts\nfunction isObject(item) {\n\treturn !!item && typeof item === \"object\" && !Array.isArray(item);\n}\n//#endregion\n//#region src/helpers/error.ts\nfunction isError(input) {\n\tif (!isObject(input)) return false;\n\tif (input instanceof Error) return true;\n\treturn typeof input.message === \"string\" && (typeof input.name === \"string\" || typeof input.stack === \"string\");\n}\n//#endregion\n//#region src/helpers/instanceof.ts\n/**\n* Property name used to store the cross-realm class-marker chain on instances.\n*\n* The chain is a `symbol[]` of `Symbol.for(...)` markers — one per class in the\n* inheritance path. Stored as a non-enumerable, non-writable, non-configurable\n* property so it stays out of `Object.keys()` and plain `JSON.stringify()`\n* output. `BaseError.toJSON()` re-emits it under the same key as a string\n* list (see {@link serializeInstanceofChain}), so on objects rehydrated from\n* that JSON the property holds a `string[]` instead.\n*\n* A well-formed chain always begins with `BASE_ERROR_INSTANCE` — every real\n* ebec error is a `BaseError` first, and subclass markers are appended on\n* top of it. Hand-built cross-realm stand-ins that carry a subclass marker\n* without it can invert that hierarchy (e.g. matching `@ebec/http`'s\n* `isHTTPError` while failing `@ebec/core`'s `isBaseError`).\n*/\nconst INSTANCEOF_PROPERTY = \"@instanceof\";\n/**\n* Append a class-marker symbol to the receiver's `@instanceof` chain.\n*\n* Each error class declares a `Symbol.for(...)` marker for itself and calls\n* `markInstanceof(this, MY_MARKER)` from its constructor. Subclass instances\n* accumulate markers from every ancestor in the chain, so a parent-class\n* guard can fast-path-match a subclass instance.\n*\n* Idempotent — re-marking the same instance with the same symbol is a no-op.\n*/\nfunction markInstanceof(target, marker) {\n\tconst existing = target[INSTANCEOF_PROPERTY];\n\tif (Array.isArray(existing)) {\n\t\tif (!existing.includes(marker)) existing.push(marker);\n\t\treturn;\n\t}\n\tObject.defineProperty(target, INSTANCEOF_PROPERTY, {\n\t\tvalue: [marker],\n\t\twritable: false,\n\t\tenumerable: false,\n\t\tconfigurable: false\n\t});\n}\n/**\n* Check whether the input's `@instanceof` chain carries `marker`.\n*\n* Returns `false` for non-objects, objects without the chain, or chains\n* stored as a non-array value (defensive).\n*\n* `strict` controls which chain entries count as a match. By default\n* (`strict: true`) only the native `Symbol.for(...)` form matches, which is\n* what an in-process instance carries. Pass `strict: false` to also match\n* the marker's `description` string: symbols are dropped by\n* `JSON.stringify`, so an error rehydrated from the JSON emitted by\n* `BaseError.toJSON()` carries the marker's description string in its chain\n* instead of the symbol, and only the loose form still recognizes it.\n* {@link matchesInstanceof} is `hasInstanceof(input, marker, false)`.\n*/\nfunction hasInstanceof(input, marker, strict = true) {\n\tif (!isObject(input)) return false;\n\tconst chain = input[INSTANCEOF_PROPERTY];\n\tif (!Array.isArray(chain)) return false;\n\tif (chain.includes(marker)) return true;\n\tif (strict) return false;\n\treturn typeof marker.description === \"string\" && chain.includes(marker.description);\n}\n/**\n* Serialize the input's `@instanceof` class-marker chain to its string form.\n*\n* Symbols are dropped by `JSON.stringify`, so `BaseError.toJSON()` emits the\n* chain as the markers' description strings instead. For `Symbol.for(...)`\n* markers the description *is* the registry key, so the string form carries\n* the same identity information as the symbol form.\n*\n* String entries pass through unchanged (a rehydrated chain re-serializes\n* losslessly); anything else is dropped. Each value is emitted once — a\n* re-marked rehydrated chain can hold the same marker in both its string\n* and symbol form.\n*/\nfunction serializeInstanceofChain(input) {\n\tif (!isObject(input)) return [];\n\tconst chain = input[INSTANCEOF_PROPERTY];\n\tif (!Array.isArray(chain)) return [];\n\tconst output = [];\n\tfor (const entry of chain) {\n\t\tlet value;\n\t\tif (typeof entry === \"symbol\") {\n\t\t\tif (entry.description) value = entry.description;\n\t\t} else if (typeof entry === \"string\") value = entry;\n\t\tif (typeof value === \"string\" && !output.includes(value)) output.push(value);\n\t}\n\treturn output;\n}\n/**\n* Check whether the input's `@instanceof` chain carries `marker` — either as\n* the native registry symbol (an in-process instance) or as the symbol's\n* description string (an error rehydrated from the JSON emitted by\n* `BaseError.toJSON()`).\n*\n* Prefer this over the strict (default) form of {@link hasInstanceof} as the\n* fast path of duck-type guards. Guards have no shape-based fallback, so the\n* strict form is the only check one performs: it matches only the symbol\n* form, so a guard built on it returns `false` outright for a\n* JSON-rehydrated subclass error — there is no slower path to fall back to.\n* Equivalent to `hasInstanceof(input, marker, false)`.\n*/\nfunction matchesInstanceof(input, marker) {\n\treturn hasInstanceof(input, marker, false);\n}\n//#endregion\n//#region src/helpers/check.ts\nconst BASE_ERROR_INSTANCE$1 = Symbol.for(\"@ebec/core/BaseError\");\nfunction isBaseError(input) {\n\treturn matchesInstanceof(input, BASE_ERROR_INSTANCE$1);\n}\nfunction isBaseErrorGroup(input) {\n\tif (!isBaseError(input)) return false;\n\tconst { errors } = input;\n\treturn Array.isArray(errors) && errors.length > 0 && errors.every((e) => isError(e));\n}\n//#endregion\n//#region src/helpers/error-code.ts\nfunction isErrorWithCode(error, code) {\n\tif (!isBaseError(error)) return false;\n\tif (Array.isArray(code)) return code.includes(error.code);\n\treturn error.code === code;\n}\n//#endregion\n//#region src/helpers/interpolate.ts\n/**\n* Substitute `{name}` placeholders in `str` with values from `data`.\n*\n* Placeholders whose key is missing from `data` (or explicitly\n* `undefined`) are left in the string verbatim, so a partially-populated\n* `data` degrades to a readable template rather than to `\"undefined\"`.\n*\n* - `regex` must carry the `g` flag — `String.prototype.matchAll` throws\n* without it.\n* - Substitution goes through `String.prototype.replace` with a string\n* replacement, so `$&`, `` $` ``, `$'` and `$1` inside a *substituted\n* value* are interpreted as replacement patterns rather than literals.\n* Treat `data` values as trusted, or escape `$` before passing them.\n*/\nfunction interpolate(str, data, regex = /\\{(\\w+)\\}/g) {\n\treturn Array.from(str.matchAll(regex)).reduce((acc, match) => {\n\t\tconst key = match[1];\n\t\tif (key && typeof data[key] !== \"undefined\") return acc.replace(match[0], String(data[key]));\n\t\treturn acc;\n\t}, str);\n}\n//#endregion\n//#region src/helpers/sanitize-code.ts\n/**\n* Convert a PascalCase class name to CONSTANT_CASE error code.\n* e.g. \"NotFoundError\" -> \"NOT_FOUND_ERROR\"\n*/\nfunction sanitizeErrorCode(input) {\n\treturn input.replace(/([a-z])([A-Z])/g, \"$1_$2\").replace(/([A-Z]+)([A-Z][a-z])/g, \"$1_$2\").toUpperCase();\n}\n//#endregion\n//#region src/helpers/serialize.ts\nfunction toSerializable(input) {\n\tif (isObject(input) && typeof input.toJSON === \"function\") return input.toJSON();\n\tif (isError(input)) return { message: input.message };\n\treturn input;\n}\n//#endregion\n//#region src/options/module.ts\nfunction isErrorOptions(input) {\n\tif (!isObject(input)) return false;\n\tif (typeof input.message !== \"undefined\" && typeof input.message !== \"string\") return false;\n\tif (typeof input.stack !== \"undefined\" && typeof input.stack !== \"string\") return false;\n\treturn typeof input.code === \"undefined\" || typeof input.code === \"string\";\n}\nfunction extractErrorOptions(input = {}) {\n\tif (typeof input === \"string\") return { message: input };\n\treturn input;\n}\n//#endregion\n//#region src/module.ts\nconst BASE_ERROR_INSTANCE = Symbol.for(\"@ebec/core/BaseError\");\nvar BaseError = class extends Error {\n\t/**\n\t* A unique identifier for the error.\n\t*/\n\tcode;\n\t/**\n\t* Represents the underlying cause or source of the error.\n\t*/\n\tcause;\n\t/**\n\t* A collection of errors for batch/group error scenarios.\n\t*/\n\terrors;\n\t/**\n\t* Structured validation failures, as an issue tree.\n\t* Always an array — empty when the error carries none.\n\t*/\n\tissues;\n\tconstructor(input = {}) {\n\t\tconst options = extractErrorOptions(input);\n\t\tlet message = options.message || \"An error occurred\";\n\t\tif (options.messageData) message = interpolate(message, options.messageData);\n\t\tsuper(message);\n\t\tif (options.cause !== void 0) this.cause = options.cause;\n\t\tif (typeof this.name === \"undefined\" || this.name === \"Error\") Object.defineProperty(this, \"name\", {\n\t\t\tconfigurable: true,\n\t\t\tenumerable: false,\n\t\t\tvalue: this.constructor.name,\n\t\t\twritable: true\n\t\t});\n\t\tif (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);\n\t\tif (options.stack) this.stack = options.stack;\n\t\tthis.code = options.code || sanitizeErrorCode(this.constructor.name);\n\t\tif (options.errors !== void 0) this.errors = [...options.errors];\n\t\tthis.issues = Array.isArray(options.issues) ? [...options.issues] : [];\n\t\tmarkInstanceof(this, BASE_ERROR_INSTANCE);\n\t}\n\t/**\n\t* The class-marker chain rides along as a string list so the ancestor\n\t* information survives a JSON round-trip (symbols don't serialize) —\n\t* duck-type guards match rehydrated subclass errors through it via\n\t* `matchesInstanceof`.\n\t*/\n\ttoJSON() {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tmessage: this.message,\n\t\t\tcode: this.code,\n\t\t\t...this.cause !== void 0 && { cause: toSerializable(this.cause) },\n\t\t\t...this.errors !== void 0 && { errors: this.errors.map((e) => toSerializable(e)) },\n\t\t\t...this.issues.length > 0 && { issues: [...this.issues] },\n\t\t\t[INSTANCEOF_PROPERTY]: serializeInstanceofChain(this)\n\t\t};\n\t}\n};\n//#endregion\n//#region src/catalog.ts\nfunction defineErrorCatalog(definitions) {\n\tconst catalog = {};\n\tfor (const key of Object.keys(definitions)) {\n\t\tconst entry = definitions[key];\n\t\tcatalog[key] = ((messageData, overrides = {}) => new BaseError({\n\t\t\t...entry,\n\t\t\t...overrides,\n\t\t\tcode: overrides.code ?? entry.code ?? key,\n\t\t\tmessage: overrides.message ?? entry.message,\n\t\t\tmessageData: overrides.messageData ?? messageData\n\t\t}));\n\t}\n\treturn catalog;\n}\n//#endregion\n//#region src/issue/check.ts\nfunction isObject$1(input) {\n\treturn !!input && typeof input === \"object\" && !Array.isArray(input);\n}\nfunction isIssuePath(input) {\n\tif (!Array.isArray(input)) return false;\n\treturn input.every((el) => typeof el === \"string\" || typeof el === \"number\" || typeof el === \"symbol\");\n}\nfunction isBaseIssue(input) {\n\tif (!isObject$1(input)) return false;\n\tif (typeof input.message !== \"string\") return false;\n\tif (!isIssuePath(input.path)) return false;\n\treturn typeof input.meta === \"undefined\" || isObject$1(input.meta);\n}\n/**\n* Structural check for a leaf node.\n*\n* Deliberately duck-typed rather than `instanceof`-based: issues cross\n* package and realm boundaries, so a tree assembled from two libraries —\n* or from two copies of this package — must still validate.\n*/\nfunction isIssueItem(input) {\n\tif (!isBaseIssue(input)) return false;\n\tif (input.type !== \"item\") return false;\n\treturn typeof input.code === \"string\";\n}\n/**\n* Structural check for a node with children. Recurses, so a group only\n* passes when every descendant is itself well-formed.\n*/\nfunction isIssueGroup(input) {\n\tif (!isBaseIssue(input)) return false;\n\tif (input.type !== \"group\") return false;\n\treturn Array.isArray(input.issues) && input.issues.every((issue) => isIssueItem(issue) || isIssueGroup(issue));\n}\n/**\n* Structural check for any node of the tree.\n*/\nfunction isIssue(input) {\n\treturn isIssueGroup(input) || isIssueItem(input);\n}\n//#endregion\n//#region src/issue/constants.ts\n/**\n* Vocabulary of well-known issue codes. Validators map their own failure\n* kinds onto these so consumer-side translation catalogs can ship one\n* localized message per code instead of falling back to a generic\n* \"invalid value\".\n*\n* Tracks the common ground between vuelidate, zod, joi, yup and\n* validator.js. The list lifts from vuelidate's catalog (snake_cased);\n* modern format codes (`PATTERN`, `UUID`, `DATE`, `JSON`, `BASE64`,\n* `STRONG_PASSWORD`, …) cover ground vuelidate predates. Every format\n* code names the format positively (`UUID`, `EMAIL`, `URL`, …) rather\n* than the failure mode (`NOT_UUID`, `INVALID_EMAIL`) so the registry\n* reads consistently.\n*\n* **This vocabulary is a default, not a requirement.** `IssueItem.code`\n* is widened to `IssueCode | (string & {})`, so a library with its own\n* vocabulary — `rapiq`'s `key_not_allowed`, an app's `email_taken` —\n* emits well-formed issues without touching this const at all. If you\n* want a typed const for your own codes, define one alongside this one:\n*\n* ```ts\n* import { IssueCode } from '@ebec/core';\n*\n* export const AppCode = {\n* ...IssueCode,\n* EMAIL_TAKEN: 'email_taken',\n* } as const;\n* ```\n*\n* Each entry's JSDoc documents the structured `data` producers should\n* attach when constructing the issue — templates can rely on those\n* placeholders being present (`{min}`, `{max}`, `{other}`, etc.).\n*/\nconst IssueCode = {\n\t/** Generic fallback when no more-specific code applies. `data`: — */\n\tVALUE_INVALID: \"value_invalid\",\n\t/** Every branch of a `oneOf` alternative failed. `data`: — */\n\tONE_OF_FAILED: \"one_of_failed\",\n\t/** Value is missing, `undefined`, `null`, or empty per the validator's semantics. `data`: — */\n\tREQUIRED: \"required\",\n\t/** Value contains characters outside the alphabetical set. `data`: — */\n\tALPHA: \"alpha\",\n\t/** Value contains characters outside the alphanumeric set. `data`: — */\n\tALPHA_NUM: \"alpha_num\",\n\t/** Value is not a number. `data`: — */\n\tNUMERIC: \"numeric\",\n\t/** Value is not an integer. `data`: — */\n\tINTEGER: \"integer\",\n\t/** Value is not a decimal number. `data`: — */\n\tDECIMAL: \"decimal\",\n\t/** Value is shorter than the configured minimum. `data`: `{ min: number }` */\n\tMIN_LENGTH: \"min_length\",\n\t/** Value is longer than the configured maximum. `data`: `{ max: number }` */\n\tMAX_LENGTH: \"max_length\",\n\t/** Numeric value is below the configured minimum. `data`: `{ min: number }` */\n\tMIN_VALUE: \"min_value\",\n\t/** Numeric value is above the configured maximum. `data`: `{ max: number }` */\n\tMAX_VALUE: \"max_value\",\n\t/** Numeric value falls outside the configured `[min, max]` range. `data`: `{ min: number, max: number }` */\n\tBETWEEN: \"between\",\n\t/** Value is not a valid email address. `data`: — */\n\tEMAIL: \"email\",\n\t/** Value is not a valid URL. `data`: — */\n\tURL: \"url\",\n\t/** Value is not a valid IP address. `data`: — */\n\tIP_ADDRESS: \"ip_address\",\n\t/** Value is not a valid MAC address. `data`: — */\n\tMAC_ADDRESS: \"mac_address\",\n\t/** Value is not a valid UUID. `data`: — */\n\tUUID: \"uuid\",\n\t/** Value is not a valid date / cannot be parsed as a date. `data`: — */\n\tDATE: \"date\",\n\t/**\n\t* Value does not match the expected regex pattern. `data`:\n\t* `{ pattern: string }` — source of the regex without flags so\n\t* catalogs can quote it in human-readable form.\n\t*/\n\tPATTERN: \"pattern\",\n\t/** Value is not valid JSON. `data`: — */\n\tJSON: \"json\",\n\t/** Value is not valid base64. `data`: — */\n\tBASE64: \"base64\",\n\t/**\n\t* Value does not meet the configured strong-password rules. `data`:\n\t* `{ minLength?: number, minLowercase?: number, minUppercase?: number,\n\t* minNumbers?: number, minSymbols?: number }` — only the keys the\n\t* producer chose to surface; missing keys mean \"the default for that\n\t* setting wasn't configured.\"\n\t*/\n\tSTRONG_PASSWORD: \"strong_password\",\n\t/**\n\t* Value must equal another named field's value (sibling-field assertion,\n\t* e.g. password-confirm). `data`: `{ other: string }` — the name of\n\t* the field being compared against.\n\t*/\n\tSAME_AS: \"same_as\"\n};\n//#endregion\n//#region src/issue/define.ts\n/**\n* Build an `IssueItem`. TS uses the supplied `code` to pick the right\n* `data` requirement at the call site — passing `MIN_LENGTH` without\n* `data: { min }` is a compile error; passing `STRONG_PASSWORD` with\n* `data: { pointsPerUnique: 5 }` is a compile error.\n*\n* `code` may be omitted (defaults to `IssueCode.VALUE_INVALID` — bare,\n* no data).\n*\n* ```ts\n* defineIssueItem({\n* code: IssueCode.MIN_LENGTH,\n* path: ['name'],\n* message: 'Must be at least 3 characters',\n* data: { min: 3 },\n* });\n* ```\n*/\nfunction defineIssueItem(data) {\n\treturn {\n\t\ttype: \"item\",\n\t\t...data,\n\t\tcode: data.code || IssueCode.VALUE_INVALID\n\t};\n}\n/**\n* Build an {@link IssueGroup} — a node that carries child issues.\n*\n* The children's `path` values are used as given; this factory does not\n* rewrite them. When the children came from validating a *sub*-structure,\n* rebase them with {@link prefixIssuePath} first so the tree keeps its\n* absolute-path invariant.\n*/\nfunction defineIssueGroup(data) {\n\treturn {\n\t\ttype: \"group\",\n\t\t...data\n\t};\n}\n//#endregion\n//#region src/issue/flatten.ts\n/**\n* Collect every leaf of the tree, in pre-order, discarding grouping.\n*\n* This is the workhorse for consumers that render per-field errors: since\n* every node carries an absolute `path` (see `IssueBase.path`), the result\n* can be indexed by field directly, with no tree walk to reassemble\n* prefixes.\n*\n* Returned items are the **same object references** held by the tree, not\n* copies — cheap to call, but do not mutate what you get back.\n*/\nfunction flattenIssueItems(issues) {\n\tconst output = [];\n\tfor (const issue of issues) if (isIssueItem(issue)) output.push(issue);\n\telse output.push(...flattenIssueItems(issue.issues));\n\treturn output;\n}\n/**\n* Collect every group of the tree, in pre-order, including nested groups.\n*\n* A group is emitted before its own descendants are visited, so the result\n* reads outermost-first. As with {@link flattenIssueItems}, entries are\n* live references into the tree.\n*/\nfunction flattenIssueGroups(issues) {\n\tconst output = [];\n\tfor (const issue of issues) if (isIssueGroup(issue)) {\n\t\toutput.push(issue);\n\t\toutput.push(...flattenIssueGroups(issue.issues));\n\t}\n\treturn output;\n}\n//#endregion\n//#region src/issue/format.ts\n/**\n* Render an issue's user-facing message.\n*\n* Resolution order:\n* 1. If `templates[code]` exists, return `interpolate(template, issue.data)`.\n* 2. Else return `issue.message` (the default English rendering set at\n* construction time).\n* 3. Else return `fallback`.\n*\n* The eager `message` written at construction time is what makes step 2\n* useful: a consumer with no catalog at all still gets something readable,\n* and a consumer with a partial catalog falls back per-code rather than\n* all-or-nothing.\n*/\nfunction formatIssue(issue, templates, fallback = \"\") {\n\tif (templates && issue.code) {\n\t\tconst template = templates[issue.code];\n\t\tif (typeof template === \"string\") return interpolate(template, issue.data || {});\n\t}\n\tif (typeof issue.message === \"string\" && issue.message.length > 0) return issue.message;\n\treturn fallback;\n}\n//#endregion\n//#region src/issue/prefix.ts\n/**\n* Rebase an issue onto a parent path, recursing into groups so every\n* descendant is rebased too.\n*\n* This is the step that maintains the absolute-path invariant (see\n* `IssueBase.path`). Whenever a producer validates a *sub*-structure and\n* merges the resulting issues into a larger tree, the children arrive with\n* paths relative to that sub-structure and have to be rewritten. Doing it\n* only at the top level is the tempting bug: a child that already wrapped\n* its own failures in an {@link IssueGroup} would surface inner leaves\n* missing the parent segment, and every consumer relying on\n* {@link flattenIssueItems} for per-field lookup would silently mis-index\n* exactly the nested cases.\n*\n* ```ts\n* // validating `address` produced issues at ['street']\n* const rebased = issues.map((issue) => prefixIssuePath(issue, ['address']));\n* // → paths are now ['address', 'street'], at every depth\n* ```\n*\n* **Returns copies.** The input is never mutated: each visited node is\n* shallow-copied and a group's `issues` array is rebuilt from the rebased\n* children, so the whole spine is fresh. `data` and `meta` are carried over\n* by reference — cheap, but it means a caller that mutates `meta` on the\n* result also mutates it on the original. Copying happens unconditionally,\n* including for an empty `prefix`.\n*/\nfunction prefixIssuePath(issue, prefix) {\n\tconst prefixed = {\n\t\t...issue,\n\t\tpath: [...prefix, ...issue.path || []]\n\t};\n\tif (prefixed.type === \"group\") prefixed.issues = prefixed.issues.map((nested) => prefixIssuePath(nested, prefix));\n\treturn prefixed;\n}\n//#endregion\nexport { BASE_ERROR_INSTANCE, BaseError, INSTANCEOF_PROPERTY, IssueCode, defineErrorCatalog, defineIssueGroup, defineIssueItem, extractErrorOptions, flattenIssueGroups, flattenIssueItems, formatIssue, hasInstanceof, interpolate, isBaseError, isBaseErrorGroup, isError, isErrorOptions, isErrorWithCode, isIssue, isIssueGroup, isIssueItem, isObject, markInstanceof, matchesInstanceof, prefixIssuePath, sanitizeErrorCode, serializeInstanceofChain, toSerializable };\n\n//# sourceMappingURL=index.mjs.map"],"x_google_ignoreList":[0],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmVA,MAAM,YAAY;;CAEjB,eAAe;;CAEf,eAAe;;CAEf,UAAU;;CAEV,OAAO;;CAEP,WAAW;;CAEX,SAAS;;CAET,SAAS;;CAET,SAAS;;CAET,YAAY;;CAEZ,YAAY;;CAEZ,WAAW;;CAEX,WAAW;;CAEX,SAAS;;CAET,OAAO;;CAEP,KAAK;;CAEL,YAAY;;CAEZ,aAAa;;CAEb,MAAM;;CAEN,MAAM;;;;;;CAMN,SAAS;;CAET,MAAM;;CAEN,QAAQ;;;;;;;;CAQR,iBAAiB;;;;;;CAMjB,SAAS;AACV;;;;;;;;;;;;;;;;;;;AAqBA,SAAS,gBAAgB,MAAM;CAC9B,OAAO;EACN,MAAM;EACN,GAAG;EACH,MAAM,KAAK,QAAQ,UAAU;CAC9B;AACD"}