@baeta/auth 0.0.0 → 2.0.0-next.15

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 (36) hide show
  1. package/CHANGELOG.md +377 -0
  2. package/LICENSE +21 -0
  3. package/README.md +171 -0
  4. package/dist/index.d.ts +204 -0
  5. package/dist/index.js +426 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +66 -2
  8. package/coverage/lcov.info +0 -1084
  9. package/coverage/tmp/coverage-80733-1779319154081-1.json +0 -1
  10. package/coverage/tmp/coverage-80733-1779319154102-0.json +0 -1
  11. package/coverage/tmp/coverage-80734-1779319150108-0.json +0 -1
  12. package/coverage/tmp/coverage-80760-1779319154037-1.json +0 -1
  13. package/coverage/tmp/coverage-80760-1779319154052-0.json +0 -1
  14. package/coverage/tmp/coverage-80769-1779319153341-13.json +0 -1
  15. package/coverage/tmp/coverage-80769-1779319153366-12.json +0 -1
  16. package/coverage/tmp/coverage-80769-1779319153366-15.json +0 -1
  17. package/coverage/tmp/coverage-80769-1779319153377-2.json +0 -1
  18. package/coverage/tmp/coverage-80769-1779319153386-19.json +0 -1
  19. package/coverage/tmp/coverage-80769-1779319153386-5.json +0 -1
  20. package/coverage/tmp/coverage-80769-1779319153392-8.json +0 -1
  21. package/coverage/tmp/coverage-80769-1779319153407-7.json +0 -1
  22. package/coverage/tmp/coverage-80769-1779319153429-21.json +0 -1
  23. package/coverage/tmp/coverage-80769-1779319153447-9.json +0 -1
  24. package/coverage/tmp/coverage-80769-1779319153544-20.json +0 -1
  25. package/coverage/tmp/coverage-80769-1779319153563-4.json +0 -1
  26. package/coverage/tmp/coverage-80769-1779319153765-16.json +0 -1
  27. package/coverage/tmp/coverage-80769-1779319153787-6.json +0 -1
  28. package/coverage/tmp/coverage-80769-1779319153805-17.json +0 -1
  29. package/coverage/tmp/coverage-80769-1779319153816-14.json +0 -1
  30. package/coverage/tmp/coverage-80769-1779319153828-11.json +0 -1
  31. package/coverage/tmp/coverage-80769-1779319153850-3.json +0 -1
  32. package/coverage/tmp/coverage-80769-1779319153897-18.json +0 -1
  33. package/coverage/tmp/coverage-80769-1779319153922-10.json +0 -1
  34. package/coverage/tmp/coverage-80769-1779319153954-0.json +0 -1
  35. package/coverage/tmp/coverage-80769-1779319153963-1.json +0 -1
  36. package/coverage/tmp/coverage-80769-1779319153972-0.json +0 -1
@@ -0,0 +1,204 @@
1
+ import { AppPlugin, FieldUsePlugin, SubscriptionUsePlugin, TypeUsePlugin } from "@baeta/core/sdk";
2
+ import { ResolverParams } from "@baeta/core";
3
+
4
+ //#region lib/error.d.ts
5
+ /** Custom error resolver function for authorization failures. */
6
+ type ScopeErrorResolver = (err: unknown) => unknown;
7
+ /**
8
+ * Default error resolver for authorization failures.
9
+ * If multiple authorization errors are encountered they are combined into `AggregateGraphQLError` with proper HTTP status codes.
10
+ */
11
+ declare function aggregateErrorResolver(err: AggregateError): any;
12
+ //#endregion
13
+ //#region lib/grant.d.ts
14
+ /**
15
+ * Represents the result of a grant operation.
16
+ * Can be either a single grant or an array of grants defined in AuthExtension.GrantsMap.
17
+ */
18
+ type GetGrantResult<Grant extends string, Result> = Grant | Grant[] | GrantConfig<Grant, Result> | GrantConfig<Grant, Result>[];
19
+ /**
20
+ * Attaches a grant to a specific object derived from the resolver result,
21
+ * instead of the result itself. For array results, `target` is invoked per
22
+ * entry. `target` must return a non-primitive value.
23
+ */
24
+ type GrantConfig<Grant extends string, Result> = {
25
+ grant: Grant | Grant[];
26
+ target: (result: GrantTarget<Result>) => unknown;
27
+ };
28
+ type GrantTarget<Result> = Result extends Array<infer U> ? U : Result;
29
+ /**
30
+ * Function that determines grants based on resolver parameters and result.
31
+ * Used for dynamic permission granting based on resolved data.
32
+ */
33
+ type GetGrantFn<Grants extends string, Result, Source, Context, Args, Info> = (params: ResolverParams<Source, Context, Args, Info>, result: Result) => GetGrantResult<Grants, Result> | PromiseLike<GetGrantResult<Grants, Result>>;
34
+ /**
35
+ * Union type for grant specifications.
36
+ * Can be either a static grant result or a function that determines grants dynamically.
37
+ */
38
+ type GetGrant<Grants extends string, Result, Source, Context, Args, Info> = GetGrantFn<Grants, Result, Source, Context, Args, Info> | GetGrantResult<Grants, Result>;
39
+ //#endregion
40
+ //#region lib/scope-rules.d.ts
41
+ type LogicRule = 'and' | 'or' | 'chain' | 'race';
42
+ type ScopesShape = Record<string, unknown>;
43
+ /**
44
+ * Defines the structure of authorization scope rules.
45
+ * Combines individual scope rules with logical operators and granted permissions.
46
+ */
47
+ type ScopeRules<Scopes extends ScopesShape, Grants extends string> = ScopeRule<Scopes, Grants> | ScopeLogicRule<Scopes, Grants>;
48
+ /**
49
+ * Utility type that enforces boolean scopes must be true.
50
+ * For non-boolean scopes, preserves the original type.
51
+ */
52
+ type ScopeRule<Scopes extends ScopesShape, Grants extends string> = { [K in keyof Scopes]: {
53
+ type: 'scope';
54
+ key: K;
55
+ value: Scopes[K] extends boolean ? true : Scopes[K];
56
+ } }[keyof Scopes] | {
57
+ type: 'grant';
58
+ grant: Grants;
59
+ };
60
+ type ScopeLogicRule<Scopes extends ScopesShape, Grants extends string> = {
61
+ type: 'rule';
62
+ rule: LogicRule;
63
+ scopes: ScopeRules<Scopes, Grants>[];
64
+ };
65
+ //#endregion
66
+ //#region lib/scope-cache-keys.d.ts
67
+ /**
68
+ * Builds a cache key for a single scope. The returned value must be stable —
69
+ * equal inputs must produce a value that compares equal as a `Map` key (a
70
+ * string, or a stable object reference).
71
+ */
72
+ type ScopeCacheKeyFn<Param> = (param: Param) => unknown;
73
+ /**
74
+ * Provide an entry when the scope's argument can't be safely
75
+ * auto-serialized in a stable manner or when a more compact key
76
+ * is preferable.
77
+ */
78
+ type ScopeCacheKeyMap<Scopes extends ScopesShape> = { [K in keyof Scopes as Scopes[K] extends boolean ? never : K]?: ScopeCacheKeyFn<Scopes[K]> };
79
+ //#endregion
80
+ //#region lib/scope-defaults.d.ts
81
+ /** Configuration for default authorization scopes that apply to all operations of a specific type. */
82
+ type DefaultScopes<Scopes extends ScopesShape, Grants extends string> = {
83
+ /** Default scopes applied to all Query operations */Query?: ScopeRules<Scopes, Grants>; /** Default scopes applied to all Mutation operations */
84
+ Mutation?: ScopeRules<Scopes, Grants>; /** Default scopes for Subscription operations */
85
+ Subscription?: ScopeRules<Scopes, Grants>;
86
+ };
87
+ //#endregion
88
+ //#region lib/scope-resolver.d.ts
89
+ /**
90
+ * Function that creates scope loaders for authorization checks.
91
+ * Returns a map of scope loaders that can be synchronous or asynchronous.
92
+ *
93
+ * @param ctx - The application context
94
+ * @returns A map of scope loaders or a promise resolving to scope loaders
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * const getScopeLoader: GetScopeLoader<Context> = (ctx) => ({
99
+ * isLoggedIn: async () => {
100
+ * if (!ctx.userId) throw new UnauthenticatedError();
101
+ * return true;
102
+ * },
103
+ * hasAccess: (role) => ctx.user?.role === role
104
+ * });
105
+ * ```
106
+ */
107
+ type GetScopeLoader<Scopes extends ScopesShape, Ctx> = (ctx: Ctx) => ScopeLoaderMap<Scopes> | Promise<ScopeLoaderMap<Scopes>>;
108
+ /**
109
+ * Represents a scope loader that can be either a boolean value or a function.
110
+ * Function loaders receive the scope value and return a boolean result.
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * // Boolean loader
115
+ * const publicLoader: ScopeLoader<boolean> = true;
116
+ *
117
+ * // Function loader
118
+ * const roleLoader: ScopeLoader<string> = (role) => userRole === role;
119
+ * ```
120
+ */
121
+ type ScopeLoader<T> = T extends boolean ? boolean | (() => boolean | Promise<boolean>) : (param: T) => boolean | Promise<boolean>;
122
+ /**
123
+ * Maps scope names to their respective loaders.
124
+ * Each loader handles authorization checks for its scope.
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const loaders: ScopeLoaderMap = {
129
+ * isPublic: true,
130
+ * isLoggedIn: () => Boolean(ctx.userId),
131
+ * hasAccess: (role) => ctx.user?.roles.includes(role)
132
+ * };
133
+ * ```
134
+ */
135
+ type ScopeLoaderMap<Scopes extends ScopesShape> = { [K in keyof Scopes]: Scopes[K] extends boolean ? boolean | (() => boolean | Promise<boolean>) : (param: Scopes[K]) => boolean | Promise<boolean> };
136
+ //#endregion
137
+ //#region lib/auth-middlewares.d.ts
138
+ /**
139
+ * Options for authorization middlewares
140
+ */
141
+ interface AuthMiddlewareOptions<Grants extends string, Result, Source, Context, Args, Info> {
142
+ /** Permissions to grant after successful authorization */
143
+ grants?: GetGrant<Grants, Result, Source, Context, Args, Info>;
144
+ /** Whether to skip default scopes for this operation */
145
+ skipDefaults?: boolean;
146
+ }
147
+ /**
148
+ * Function to get scope rules for pre-resolution authorization
149
+ */
150
+ type GetScopeRules<Scopes extends ScopesShape, Grants extends string, Source, Context, Args, Info> = (params: ResolverParams<Source, Context, Args, Info>) => boolean | ScopeRules<Scopes, Grants> | PromiseLike<boolean | ScopeRules<Scopes, Grants>>;
151
+ /**
152
+ * Function to get scope rules for post-resolution authorization
153
+ */
154
+ type GetPostScopeRules<Scopes extends ScopesShape, Grants extends string, Result, Source, Context, Args, Info> = (params: ResolverParams<Source, Context, Args, Info>, result: Result) => boolean | ScopeRules<Scopes, Grants> | PromiseLike<boolean | ScopeRules<Scopes, Grants>>;
155
+ //#endregion
156
+ //#region lib/define-rules.d.ts
157
+ type RuleAccessor<Scopes extends ScopesShape, Grants extends string> = {
158
+ and(scope: ScopeRules<Scopes, Grants>, ...scopes: ScopeRules<Scopes, Grants>[]): ScopeRules<Scopes, Grants>;
159
+ or(scope: ScopeRules<Scopes, Grants>, ...scopes: ScopeRules<Scopes, Grants>[]): ScopeRules<Scopes, Grants>;
160
+ chain(scope: ScopeRules<Scopes, Grants>, ...scopes: ScopeRules<Scopes, Grants>[]): ScopeRules<Scopes, Grants>;
161
+ race(scope: ScopeRules<Scopes, Grants>, ...scopes: ScopeRules<Scopes, Grants>[]): ScopeRules<Scopes, Grants>;
162
+ };
163
+ //#endregion
164
+ //#region lib/define-scopes.d.ts
165
+ type ScopeAccessor<Scopes extends ScopesShape, Grants extends string> = { [K in keyof Scopes]: Scopes[K] extends boolean ? ScopeRules<Scopes, Grants> : (param: Scopes[K]) => ScopeRules<Scopes, Grants> } & {
166
+ $granted: <G extends Grants>(grant: G) => ScopeRule<Scopes, G>;
167
+ };
168
+ //#endregion
169
+ //#region lib/create-auth.d.ts
170
+ type AuthPlugin<Result, Source, Context, Args, Info> = FieldUsePlugin<Result, Source, Context, Args, Info> & TypeUsePlugin<Source, Context, Info> & SubscriptionUsePlugin<Result, Source, Context, Args, Info, 'resolve'> & SubscriptionUsePlugin<Result, Source, Context, Args, Info, 'subscribe'>;
171
+ /** Configuration options for Auth */
172
+ interface AuthOptions<Scopes extends ScopesShape, Grants extends string> {
173
+ /** Default authorization scopes for queries, mutations or subscriptions */
174
+ defaultScopes?: (opt: {
175
+ scope: ScopeAccessor<Scopes, Grants>;
176
+ rule: RuleAccessor<Scopes, Grants>;
177
+ }) => DefaultScopes<Scopes, Grants>;
178
+ /** Custom error resolver for authorization failures */
179
+ errorResolver?: ScopeErrorResolver;
180
+ /**
181
+ * Per-scope cache key overrides. Recommended for scopes whose argument
182
+ * isn't safely auto-serializable: serializable args (primitives, plain
183
+ * objects, arrays of those) are stringified automatically, and anything
184
+ * else falls back to reference identity — which may miss cache hits when
185
+ * callers construct equivalent-but-distinct values.
186
+ */
187
+ cacheKeyMap?: ScopeCacheKeyMap<Scopes>;
188
+ }
189
+ declare function createAuth<Context, Scopes extends ScopesShape, Grants extends string>(loadScopes: GetScopeLoader<Scopes, Context>, globalOptions?: AuthOptions<Scopes, Grants>): {
190
+ auth: <Result, Source, Context_1, Args, Info>(scopes: ScopeRules<Scopes, Grants> | GetScopeRules<Scopes, Grants, Source, Context_1, Args, Info>, options?: AuthMiddlewareOptions<Grants, Result, Source, Context_1, Args, Info>) => AuthPlugin<Result, Source, Context_1, Args, Info>;
191
+ authAfter: <Result, Source, Context_1, Args, Info>(getScopes: GetPostScopeRules<Scopes, Grants, Result, Source, Context_1, Args, Info>, options?: AuthMiddlewareOptions<Grants, Result, Source, Context_1, Args, Info>) => AuthPlugin<Result, Source, Context_1, Args, Info>;
192
+ authAppPlugin: AppPlugin;
193
+ rule: RuleAccessor<Scopes, Grants>;
194
+ scope: ScopeAccessor<Scopes, Grants>;
195
+ };
196
+ //#endregion
197
+ //#region lib/serialize.d.ts
198
+ type SerializableScope = string | number | boolean | null | SerializableScope[] | {
199
+ [key: string]: SerializableScope;
200
+ };
201
+ declare function createScopeCacheKey(params: SerializableScope): string;
202
+ //#endregion
203
+ export { type AuthMiddlewareOptions, type AuthOptions, type DefaultScopes, type GetGrant, type GetGrantFn, type GetGrantResult, type GetPostScopeRules, type GetScopeLoader, type GetScopeRules, type GrantConfig, type LogicRule, type ScopeCacheKeyFn, type ScopeCacheKeyMap, type ScopeErrorResolver, type ScopeLoader, type ScopeLoaderMap, type ScopeRule, type ScopeRules, type ScopesShape, type SerializableScope, aggregateErrorResolver, createAuth, createScopeCacheKey };
204
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,426 @@
1
+ import { AggregateGraphQLError, ForbiddenError, InternalServerError } from "@baeta/errors";
2
+ import { log } from "@baeta/util-log";
3
+ import { GraphQLError } from "graphql";
4
+ import { createAppPluginId, makePluginSymbol, nameFunction } from "@baeta/core/sdk";
5
+ import { createContextStore } from "@baeta/core";
6
+ import stringify from "safe-stable-stringify";
7
+ //#region lib/error.ts
8
+ function resolveError(err, resolve) {
9
+ const resolvedError = resolve(err);
10
+ if (resolvedError instanceof Error) throw resolvedError;
11
+ throw err;
12
+ }
13
+ function defaultErrorResolver(err) {
14
+ if (err instanceof AggregateError) return aggregateErrorResolver(err);
15
+ if (!isGraphqlError(err)) log.warn(`Non GraphQLError encountered by auth`, err);
16
+ return err;
17
+ }
18
+ /**
19
+ * Default error resolver for authorization failures.
20
+ * If multiple authorization errors are encountered they are combined into `AggregateGraphQLError` with proper HTTP status codes.
21
+ */
22
+ function aggregateErrorResolver(err) {
23
+ if (err.errors.length === 1) {
24
+ if (!isGraphqlError(err.errors[0])) log.warn(`Non GraphQLError encountered by auth`, err);
25
+ return err.errors[0];
26
+ }
27
+ let http = {};
28
+ const errors = [];
29
+ for (const error of err.errors) {
30
+ if (!isGraphqlError(error)) {
31
+ errors.push(new InternalServerError(error));
32
+ log.warn(`Non GraphQLError encountered by auth`, err);
33
+ continue;
34
+ }
35
+ errors.push(error);
36
+ if (error.extensions.http && http?.status !== 401) http = error.extensions.http;
37
+ }
38
+ return new AggregateGraphQLError(errors, void 0, { extensions: { http } });
39
+ }
40
+ function isGraphqlError(err) {
41
+ return err instanceof GraphQLError;
42
+ }
43
+ //#endregion
44
+ //#region utils/resolver.ts
45
+ function isOperationType(type) {
46
+ return [
47
+ "Query",
48
+ "Mutation",
49
+ "Subscription"
50
+ ].includes(type);
51
+ }
52
+ const [getAuthStore, setAuthStore] = createContextStore(Symbol("@baeta/auth"));
53
+ //#endregion
54
+ //#region lib/grant.ts
55
+ async function saveGrants(params, result, grants) {
56
+ if (result == null) return;
57
+ const [store, resolvedGrants] = await Promise.all([getAuthStore(params.ctx), resolveGrants(params, result, grants)]);
58
+ const entries = Array.isArray(result) ? result : [result];
59
+ resolvedGrants.forEach(({ grant, target }) => {
60
+ for (const entry of entries) {
61
+ if (entry == null) continue;
62
+ store.grantCache.addGrants(target(entry), grant);
63
+ }
64
+ });
65
+ }
66
+ function defaultTarget(entry) {
67
+ return entry;
68
+ }
69
+ function normalizeGrant(grants) {
70
+ if (typeof grants === "string") return {
71
+ grant: [grants],
72
+ target: defaultTarget
73
+ };
74
+ return {
75
+ grant: Array.isArray(grants.grant) ? grants.grant : [grants.grant],
76
+ target: grants.target
77
+ };
78
+ }
79
+ function normalizeGrants(grants) {
80
+ if (Array.isArray(grants)) return grants.map((el) => normalizeGrant(el));
81
+ return [normalizeGrant(grants)];
82
+ }
83
+ async function resolveGrants(params, result, grants) {
84
+ if (typeof grants !== "function") return normalizeGrants(grants);
85
+ return normalizeGrants(await grants(params, result));
86
+ }
87
+ //#endregion
88
+ //#region lib/scope-defaults.ts
89
+ function selectDefaultScopes(skipDefaults, type, defaultScopes) {
90
+ if (!defaultScopes) return;
91
+ if (!isOperationType(type)) return;
92
+ if (skipDefaults === true) return;
93
+ if (type === "Query") return defaultScopes.Query;
94
+ if (type === "Mutation") return defaultScopes.Mutation;
95
+ if (type === "Subscription") return defaultScopes.Subscription;
96
+ }
97
+ //#endregion
98
+ //#region lib/scope-rules.ts
99
+ async function verifyGrant(params, grant) {
100
+ if (grant == null) throw new Error("Grant key '$granted' must be defined in the scope rules!");
101
+ if ((await getAuthStore(params.ctx)).grantCache.getGrants(params.source)?.has(grant) !== true) throw new ForbiddenError();
102
+ return true;
103
+ }
104
+ async function verifyScope(params, scope) {
105
+ if (scope == null) throw new Error("Scope rules cannot be undefined!");
106
+ if (scope.type === "rule") return await verifyScopeRule(params, scope);
107
+ if (scope.type === "grant") return await verifyGrant(params, scope.grant);
108
+ if (scope.type === "scope") {
109
+ const resolve = (await getAuthStore(params.ctx)).scopes.get(scope.key);
110
+ if (resolve == null) throw new Error(`No scope resolver found for key '${scope.key}'!`);
111
+ if (await resolve(scope.value) !== true) throw new Error("Scope resolver should throw for non true results!");
112
+ return true;
113
+ }
114
+ throw new Error("Invalid scope rule! Must be a scope, grant, or nested rule.");
115
+ }
116
+ async function verifyScopeRule(params, scope) {
117
+ if (scope.scopes.length === 0) throw new Error("Scope rule cannot be empty!");
118
+ if (scope.rule === "chain") return await verifyChainScopes(params, scope.scopes);
119
+ if (scope.rule === "race") return await verifyRaceScopes(params, scope.scopes);
120
+ if (scope.rule === "or") return await verifyOrScopes(params, scope.scopes);
121
+ if (scope.rule === "and") return await verifyAndScopes(params, scope.scopes);
122
+ scope.rule;
123
+ throw new Error("Invalid logic rule! Must be one of 'chain', 'race', 'or', or 'and'.");
124
+ }
125
+ async function verifyChainScopes(params, scopes) {
126
+ for (const scope of scopes) await verifyScope(params, scope);
127
+ return true;
128
+ }
129
+ async function verifyRaceScopes(params, scopes) {
130
+ for (const scope of scopes) if (await verifyScope(params, scope).catch((err) => err) === true) return true;
131
+ throw new ForbiddenError();
132
+ }
133
+ async function verifyOrScopes(params, scopes) {
134
+ const promises = scopes.map((scope) => verifyScope(params, scope));
135
+ return await Promise.any(promises);
136
+ }
137
+ async function verifyAndScopes(params, scopes) {
138
+ const promises = scopes.map((scope) => verifyScope(params, scope));
139
+ await Promise.all(promises);
140
+ return true;
141
+ }
142
+ //#endregion
143
+ //#region lib/grant-cache.ts
144
+ function createGrantCache() {
145
+ const cache = /* @__PURE__ */ new WeakMap();
146
+ return {
147
+ getGrants: (result) => {
148
+ if (!isValidTarget(result)) return;
149
+ return cache.get(result);
150
+ },
151
+ addGrants: (result, values) => {
152
+ if (!isValidTarget(result)) {
153
+ log.warn(`Attempted to add grants for a non-object result. Grants will not be saved.`, (/* @__PURE__ */ new Error()).stack);
154
+ return;
155
+ }
156
+ const existing = cache.get(result) ?? /* @__PURE__ */ new Set();
157
+ values.forEach((grant) => existing.add(grant));
158
+ cache.set(result, existing);
159
+ }
160
+ };
161
+ }
162
+ function isValidTarget(target) {
163
+ return typeof target === "object" && target !== null;
164
+ }
165
+ //#endregion
166
+ //#region lib/serialize.ts
167
+ function createScopeCacheKey(params) {
168
+ return stringify(params);
169
+ }
170
+ function canSafelySerialize(value, path = /* @__PURE__ */ new WeakSet()) {
171
+ if (Array.isArray(value)) {
172
+ if (path.has(value)) return false;
173
+ path.add(value);
174
+ const ok = value.every((v) => canSafelySerialize(v, path));
175
+ path.delete(value);
176
+ return ok;
177
+ }
178
+ if (value && typeof value === "object") {
179
+ const proto = Object.getPrototypeOf(value);
180
+ if (proto !== null && proto !== Object.prototype) return false;
181
+ if (path.has(value)) return false;
182
+ path.add(value);
183
+ const ok = Object.values(value).every((v) => canSafelySerialize(v, path));
184
+ path.delete(value);
185
+ return ok;
186
+ }
187
+ return isSerializablePrimitive(value);
188
+ }
189
+ function isSerializablePrimitive(value) {
190
+ if (typeof value === "number") return Number.isFinite(value);
191
+ return value === null || typeof value === "boolean" || typeof value === "string";
192
+ }
193
+ //#endregion
194
+ //#region lib/scope-cache.ts
195
+ const noParameterKey = Symbol("no-parameter");
196
+ function createScopeCache(cacheKeyMap) {
197
+ const scopeCache = /* @__PURE__ */ new Map();
198
+ const keyFns = cacheKeyMap;
199
+ const makeKey = (scope, params) => {
200
+ if (params === void 0) return noParameterKey;
201
+ const customKeyFn = keyFns[scope];
202
+ if (customKeyFn) return customKeyFn(params);
203
+ if (canSafelySerialize(params)) return createScopeCacheKey(params);
204
+ return params;
205
+ };
206
+ return {
207
+ getScopeValue: (scope, params) => {
208
+ return scopeCache.get(scope)?.get(makeKey(scope, params));
209
+ },
210
+ setScopeValue: (scope, params, value) => {
211
+ let scopeParamsMap = scopeCache.get(scope);
212
+ if (scopeParamsMap == null) {
213
+ scopeParamsMap = /* @__PURE__ */ new Map();
214
+ scopeCache.set(scope, scopeParamsMap);
215
+ }
216
+ scopeParamsMap.set(makeKey(scope, params), value);
217
+ }
218
+ };
219
+ }
220
+ //#endregion
221
+ //#region lib/scope-resolver.ts
222
+ function resolveBoolean(param) {
223
+ if (param !== true) throw new ForbiddenError();
224
+ return true;
225
+ }
226
+ function createScopeResolver(ctx, name, value) {
227
+ if (!(typeof value === "function")) return () => resolveBoolean(value);
228
+ return async (params) => {
229
+ const store = await getAuthStore(ctx);
230
+ const cached = await store.scopeCache.getScopeValue(name, params);
231
+ if (cached != null) return resolveBoolean(cached);
232
+ const resultPromise = value(params);
233
+ store.scopeCache.setScopeValue(name, params, resultPromise);
234
+ return resolveBoolean(await resultPromise);
235
+ };
236
+ }
237
+ function createScopeResolverMap(ctx, scopeLoaderMap) {
238
+ const map = /* @__PURE__ */ new Map();
239
+ for (const [key, value] of Object.entries(scopeLoaderMap)) map.set(key, createScopeResolver(ctx, key, value));
240
+ return map;
241
+ }
242
+ //#endregion
243
+ //#region lib/store-loader.ts
244
+ function loadAuthStore(ctx, getScopeLoader, cacheKeyMap) {
245
+ setAuthStore(ctx, async () => {
246
+ return {
247
+ scopes: createScopeResolverMap(ctx, await getScopeLoader(ctx)),
248
+ scopeCache: createScopeCache(cacheKeyMap),
249
+ grantCache: createGrantCache()
250
+ };
251
+ });
252
+ }
253
+ //#endregion
254
+ //#region lib/auth-middlewares.ts
255
+ function createMiddleware(type, loadScopes, cacheKeyMap, scopes, globalScopes, options, onError) {
256
+ const getScopes = typeof scopes === "function" ? scopes : () => scopes;
257
+ const defaultScopes = selectDefaultScopes(options?.skipDefaults, type, globalScopes);
258
+ return async (next, params) => {
259
+ loadAuthStore(params.ctx, loadScopes, cacheKeyMap);
260
+ await verifyMiddlewareScopes(params, defaultScopes, await getScopes(params), onError ?? defaultErrorResolver);
261
+ const result = await next();
262
+ if (options?.grants) await saveGrants(params, result, options.grants);
263
+ return result;
264
+ };
265
+ }
266
+ function createPostMiddleware(type, loadScopes, cacheKeyMap, getScopes, globalScopes, options, onError) {
267
+ const defaultScopes = selectDefaultScopes(options?.skipDefaults, type, globalScopes);
268
+ return async (next, params) => {
269
+ loadAuthStore(params.ctx, loadScopes, cacheKeyMap);
270
+ const result = await next();
271
+ await verifyMiddlewareScopes(params, defaultScopes, await getScopes(params, result), onError ?? defaultErrorResolver);
272
+ if (options?.grants) await saveGrants(params, result, options.grants);
273
+ return result;
274
+ };
275
+ }
276
+ function createFallbackMiddleware(type, loadScopes, cacheKeyMap, globalScopes, onError) {
277
+ const rules = selectDefaultScopes(false, type, globalScopes);
278
+ if (rules == null) return;
279
+ return createMiddleware(type, loadScopes, cacheKeyMap, rules, {}, { skipDefaults: true }, onError);
280
+ }
281
+ async function verifyMiddlewareScopes(params, defaultScopes, requiredScopes, errorResolver) {
282
+ if (requiredScopes === false) throw new ForbiddenError();
283
+ const promises = [];
284
+ if (defaultScopes) promises.push(verifyScope(params, defaultScopes));
285
+ if (requiredScopes !== true) promises.push(verifyScope(params, requiredScopes));
286
+ if (promises.length === 0) return;
287
+ return await Promise.all(promises).catch((err) => resolveError(err, errorResolver));
288
+ }
289
+ //#endregion
290
+ //#region lib/define-rules.ts
291
+ function defineRules() {
292
+ return {
293
+ and(...scopes) {
294
+ return {
295
+ type: "rule",
296
+ rule: "and",
297
+ scopes
298
+ };
299
+ },
300
+ or(...scopes) {
301
+ return {
302
+ type: "rule",
303
+ rule: "or",
304
+ scopes
305
+ };
306
+ },
307
+ chain(...scopes) {
308
+ return {
309
+ type: "rule",
310
+ rule: "chain",
311
+ scopes
312
+ };
313
+ },
314
+ race(...scopes) {
315
+ return {
316
+ type: "rule",
317
+ rule: "race",
318
+ scopes
319
+ };
320
+ }
321
+ };
322
+ }
323
+ //#endregion
324
+ //#region lib/define-scopes.ts
325
+ function defineScopes() {
326
+ const cache = /* @__PURE__ */ new Map();
327
+ const resolveScope = (prop) => {
328
+ if (prop === "$granted") return (grant) => makeGrant(grant);
329
+ const leaf = makeProp(prop, true);
330
+ const fn = (param) => makeProp(prop, param);
331
+ return Object.assign(fn, leaf);
332
+ };
333
+ return new Proxy({}, { get(_target, prop) {
334
+ const cached = cache.get(prop);
335
+ if (cached) return cached;
336
+ const result = resolveScope(prop);
337
+ cache.set(prop, result);
338
+ return result;
339
+ } });
340
+ }
341
+ function makeGrant(grant) {
342
+ return {
343
+ type: "grant",
344
+ grant
345
+ };
346
+ }
347
+ function makeProp(prop, value) {
348
+ return {
349
+ type: "scope",
350
+ key: prop,
351
+ value
352
+ };
353
+ }
354
+ //#endregion
355
+ //#region lib/create-auth.ts
356
+ function createAuth(loadScopes, globalOptions = {}) {
357
+ const id = createAppPluginId("Baeta Auth");
358
+ const stateKey = Symbol("auth");
359
+ const scope = defineScopes();
360
+ const rule = defineRules();
361
+ const loadScopesFn = loadScopes;
362
+ const defaultScopes = globalOptions.defaultScopes?.({
363
+ scope,
364
+ rule
365
+ });
366
+ const cacheKeyMap = globalOptions.cacheKeyMap ?? {};
367
+ const makeAuthBuilder = (buildMiddleware) => {
368
+ return { [makePluginSymbol]: ({ type, field, subscriptionFieldKind }) => {
369
+ const middleware = buildMiddleware(type);
370
+ nameFunction(middleware, buildMiddlewareName(type, field, subscriptionFieldKind));
371
+ return {
372
+ id,
373
+ middleware,
374
+ meta: new Map([[stateKey, { hasAuth: true }]])
375
+ };
376
+ } };
377
+ };
378
+ const auth = (scopes, options) => makeAuthBuilder((type) => createMiddleware(type, loadScopesFn, cacheKeyMap, scopes, defaultScopes, options, globalOptions.errorResolver));
379
+ const authAfter = (getScopes, options) => makeAuthBuilder((type) => {
380
+ if (type === "Mutation") throw new Error("\"authAfter\" cannot be used on Mutations! authAfter is executed after the resolver thus cannot protect mutations. Use \"auth\" instead for mutations.");
381
+ return createPostMiddleware(type, loadScopesFn, cacheKeyMap, getScopes, defaultScopes, options, globalOptions.errorResolver);
382
+ });
383
+ return {
384
+ auth,
385
+ authAfter,
386
+ authAppPlugin: {
387
+ id,
388
+ name: "Baeta Auth",
389
+ mutate: (compilers) => {
390
+ if (defaultScopes == null) return;
391
+ for (const typeCompiler of iterateTypes(compilers)) {
392
+ if (!isOperationType(typeCompiler.type)) continue;
393
+ if (defaultScopes[typeCompiler.type] == null) continue;
394
+ if (hasAuth(typeCompiler.useMetadata(stateKey).get())) continue;
395
+ for (const fieldCompiler of typeCompiler.fields) {
396
+ if (hasAuth(readFieldAuthState(fieldCompiler, stateKey))) continue;
397
+ const middleware = createFallbackMiddleware(typeCompiler.type, loadScopesFn, cacheKeyMap, defaultScopes, globalOptions.errorResolver);
398
+ if (!middleware) continue;
399
+ if (fieldCompiler.kind === "Field") fieldCompiler.addTopLevelMiddleware(middleware);
400
+ else fieldCompiler.addTopLevelSubscribeMiddleware(middleware);
401
+ }
402
+ }
403
+ }
404
+ },
405
+ rule,
406
+ scope
407
+ };
408
+ }
409
+ function buildMiddlewareName(type, field, subscriptionFieldKind) {
410
+ if (field && subscriptionFieldKind) return `${type}.${field}.${subscriptionFieldKind}.$use.auth`;
411
+ if (field) return `${type}.${field}.$use.auth`;
412
+ return `${type}.$use.auth`;
413
+ }
414
+ function hasAuth(state) {
415
+ return state?.hasAuth === true;
416
+ }
417
+ function readFieldAuthState(field, key) {
418
+ return field.kind === "Field" ? field.useMetadata(key).get() : field.useSubscribeMetadata(key).get();
419
+ }
420
+ function* iterateTypes(compilers) {
421
+ for (const compiler of compilers) for (const typeCompiler of compiler.types) yield typeCompiler;
422
+ }
423
+ //#endregion
424
+ export { aggregateErrorResolver, createAuth, createScopeCacheKey };
425
+
426
+ //# sourceMappingURL=index.js.map