@cleverbrush/auth 0.0.0-beta-20260413195755

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # @cleverbrush/auth
2
+
3
+ [![CI](https://github.com/cleverbrush/framework/actions/workflows/ci.yml/badge.svg)](https://github.com/cleverbrush/framework/actions/workflows/ci.yml)
4
+ [![License: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](../../LICENSE)
5
+
6
+ Transport-agnostic authentication and authorization for TypeScript. Ships with JWT and cookie schemes, a fluent policy builder, and a typed `Principal` value object. Designed for [`@cleverbrush/server`](../server) but usable in any Node.js context.
7
+
8
+ ## Features
9
+
10
+ - **Transport-agnostic** — authentication schemes receive a plain `AuthenticationContext` (headers + cookies + items), not a raw HTTP request.
11
+ - **`Principal<T>`** — immutable, typed value object exposing `.hasRole()`, `.hasClaim()`, `.isAuthenticated`, and `.claims`.
12
+ - **JWT scheme** — `jwtScheme()` supports HS256/HS384/HS512 and RS256/RS384/RS512; configurable issuer, audience, clock tolerance, and role claim.
13
+ - **Cookie scheme** — `cookieScheme()` with a user-supplied async `validate()` function (session lookup, signature check, etc.).
14
+ - **Authorization** — `PolicyBuilder` with `.requireRole()` / `.require(predicate)` fluent API; `AuthorizationService.authorize()` accepts requirements or a named policy.
15
+ - **`requireRole()` helper** — convenience factory for single-call role requirements.
16
+ - **Cookie utilities** — `parseCookies()` and `serializeCookie()` for low-level cookie handling.
17
+ - **Zero runtime dependencies** — uses only Node.js built-in `crypto`.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @cleverbrush/auth
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```ts
28
+ import { jwtScheme, Principal } from '@cleverbrush/auth';
29
+
30
+ type UserClaims = { sub: string; role: string };
31
+
32
+ const jwt = jwtScheme<UserClaims>({
33
+ secret: process.env.JWT_SECRET!,
34
+ mapClaims: claims => ({
35
+ sub: claims.sub as string,
36
+ role: claims.role as string
37
+ })
38
+ });
39
+
40
+ // Authenticate a request context (built from HTTP headers)
41
+ const result = await jwt.authenticate({
42
+ headers: { authorization: 'Bearer <token>' },
43
+ cookies: {},
44
+ items: new Map()
45
+ });
46
+
47
+ if (result.succeeded) {
48
+ const principal: Principal<UserClaims> = result.principal;
49
+ principal.isAuthenticated; // true
50
+ principal.hasRole('admin'); // boolean
51
+ principal.value.sub; // string
52
+ }
53
+ ```
54
+
55
+ ## JWT Authentication
56
+
57
+ ```ts
58
+ import { jwtScheme, signJwt } from '@cleverbrush/auth';
59
+
60
+ // Sign a token (for testing or token issuance)
61
+ const token = signJwt({ sub: 'user-1', role: 'admin' }, 'my-secret');
62
+
63
+ // Verify and authenticate
64
+ const scheme = jwtScheme({
65
+ secret: 'my-secret',
66
+ algorithms: ['HS256'], // default
67
+ issuer: 'https://my-app.com', // optional iss check
68
+ audience: 'my-api', // optional aud check
69
+ clockTolerance: 5, // seconds
70
+ roleClaim: 'role', // default
71
+ mapClaims: c => ({ sub: c.sub as string, role: c.role as string })
72
+ });
73
+ ```
74
+
75
+ Supported algorithms: `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`.
76
+
77
+ ## Cookie Authentication
78
+
79
+ ```ts
80
+ import { cookieScheme } from '@cleverbrush/auth';
81
+
82
+ const cookie = cookieScheme<{ userId: string; role: string }>({
83
+ cookieName: 'session',
84
+ validate: async (cookieValue) => {
85
+ // Look up session from DB, verify signature, etc.
86
+ const session = await sessionStore.get(cookieValue);
87
+ if (!session) return null;
88
+ return { userId: session.userId, role: session.role };
89
+ }
90
+ });
91
+ ```
92
+
93
+ ## Authorization
94
+
95
+ ### Policy Builder
96
+
97
+ ```ts
98
+ import { PolicyBuilder, requireRole } from '@cleverbrush/auth';
99
+
100
+ const adminPolicy = new PolicyBuilder()
101
+ .requireRole('admin')
102
+ .require(principal => principal.hasClaim('verified', 'true'))
103
+ .build('admin-only');
104
+ ```
105
+
106
+ ### Authorization Service
107
+
108
+ ```ts
109
+ import { AuthorizationService } from '@cleverbrush/auth';
110
+
111
+ const policies = new Map([['admin-only', adminPolicy]]);
112
+ const authz = new AuthorizationService(policies);
113
+
114
+ // Check by requirements array
115
+ const result = await authz.authorize(principal, [requireRole('admin')]);
116
+
117
+ // Check by named policy
118
+ const result2 = await authz.authorize(principal, 'admin-only');
119
+
120
+ if (!result.allowed) {
121
+ console.log(result.reason); // 'Not authenticated' | 'Forbidden'
122
+ }
123
+ ```
124
+
125
+ ## Cookie Utilities
126
+
127
+ ```ts
128
+ import { parseCookies, serializeCookie } from '@cleverbrush/auth';
129
+
130
+ // Parse Cookie header
131
+ const cookies = parseCookies('session=abc123; theme=dark');
132
+ // { session: 'abc123', theme: 'dark' }
133
+
134
+ // Build Set-Cookie header value
135
+ const header = serializeCookie('session', 'abc123', {
136
+ httpOnly: true,
137
+ secure: true,
138
+ sameSite: 'Lax',
139
+ maxAge: 3600
140
+ });
141
+ ```
142
+
143
+ ## Integration with @cleverbrush/server
144
+
145
+ ```ts
146
+ import { ServerBuilder } from '@cleverbrush/server';
147
+ import { jwtScheme } from '@cleverbrush/auth';
148
+
149
+ const server = new ServerBuilder();
150
+
151
+ server
152
+ .useAuthentication({
153
+ defaultScheme: 'jwt',
154
+ schemes: [jwtScheme({ secret: process.env.JWT_SECRET!, mapClaims: c => c })]
155
+ })
156
+ .useAuthorization();
157
+
158
+ await server.listen(3000);
159
+ ```
160
+
161
+ ## License
162
+
163
+ BSD-3-Clause — see [LICENSE](../../LICENSE).
@@ -0,0 +1,43 @@
1
+ import type { Principal } from './Principal.js';
2
+ /**
3
+ * Transport-agnostic context passed to authentication schemes.
4
+ * HTTP middleware builds this from `RequestContext`; future transports
5
+ * (WebSocket, gRPC) provide the same shape.
6
+ */
7
+ export interface AuthenticationContext {
8
+ readonly headers: Record<string, string>;
9
+ readonly cookies: Record<string, string>;
10
+ readonly items: Map<string, unknown>;
11
+ }
12
+ /**
13
+ * The result returned by {@link AuthenticationScheme.authenticate}.
14
+ * On success, provides a typed `Principal`; on failure, an optional reason.
15
+ */
16
+ export type AuthenticationResult<T = unknown> = {
17
+ succeeded: true;
18
+ principal: Principal<T>;
19
+ } | {
20
+ succeeded: false;
21
+ failure?: string;
22
+ };
23
+ /**
24
+ * An authentication scheme extracts and validates credentials from the
25
+ * transport-agnostic `AuthenticationContext`.
26
+ */
27
+ export interface AuthenticationScheme<T = unknown> {
28
+ /** Unique name for this scheme (e.g. "jwt", "cookie"). */
29
+ readonly name: string;
30
+ /**
31
+ * Attempt to authenticate the request.
32
+ * Returns a successful result with a `Principal<T>` or a failure.
33
+ */
34
+ authenticate(context: AuthenticationContext): Promise<AuthenticationResult<T>>;
35
+ /**
36
+ * Optional challenge header for 401 responses.
37
+ * E.g. `{ headerName: 'WWW-Authenticate', headerValue: 'Bearer' }`
38
+ */
39
+ challenge?(): {
40
+ headerName: string;
41
+ headerValue: string;
42
+ };
43
+ }
@@ -0,0 +1,67 @@
1
+ import type { Principal } from './Principal.js';
2
+ /**
3
+ * A predicate that a principal must satisfy.
4
+ * Return `true` to allow, `false` to deny.
5
+ */
6
+ export type AuthorizationRequirement = (principal: Principal<any>) => boolean | Promise<boolean>;
7
+ /**
8
+ * Creates an authorization requirement that checks if the principal
9
+ * has at least one of the specified roles.
10
+ */
11
+ export declare function requireRole(...roles: string[]): AuthorizationRequirement;
12
+ /**
13
+ * A named set of authorization requirements.
14
+ * All requirements must pass (AND semantics) for the policy to be satisfied.
15
+ * Build instances using {@link PolicyBuilder}.
16
+ */
17
+ export interface AuthorizationPolicy {
18
+ readonly name: string;
19
+ readonly requirements: readonly AuthorizationRequirement[];
20
+ }
21
+ /**
22
+ * Fluent builder for composing {@link AuthorizationPolicy} instances.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const policy = new PolicyBuilder()
27
+ * .requireRole('admin')
28
+ * .require(p => p.hasClaim('verified', 'true'))
29
+ * .build('admin-only');
30
+ * ```
31
+ */
32
+ export declare class PolicyBuilder {
33
+ #private;
34
+ /** Add a role requirement: at least one of `roles` must be held by the principal. */
35
+ requireRole(...roles: string[]): this;
36
+ /** Add an arbitrary predicate requirement. */
37
+ require(requirement: AuthorizationRequirement): this;
38
+ /** Finalize the policy with the given name. */
39
+ build(name: string): AuthorizationPolicy;
40
+ }
41
+ /**
42
+ * The result of an authorization check performed by {@link AuthorizationService.authorize}.
43
+ */
44
+ export type AuthorizationResult = {
45
+ allowed: true;
46
+ } | {
47
+ allowed: false;
48
+ reason: string;
49
+ };
50
+ /**
51
+ * Evaluates authorization requirements or named policies against a principal.
52
+ *
53
+ * Instantiated internally by `ServerBuilder.useAuthorization()` or directly
54
+ * for use outside the server framework.
55
+ */
56
+ export declare class AuthorizationService {
57
+ #private;
58
+ constructor(policies?: Map<string, AuthorizationPolicy>);
59
+ /**
60
+ * Evaluate authorization for a principal.
61
+ *
62
+ * - `requirements` array: all must pass (AND semantics).
63
+ * - Named policy string: looks up registered policy, then checks its requirements.
64
+ */
65
+ authorize(principal: Principal<any>, requirements: readonly AuthorizationRequirement[]): Promise<AuthorizationResult>;
66
+ authorize(principal: Principal<any>, policyName: string): Promise<AuthorizationResult>;
67
+ }
@@ -0,0 +1,24 @@
1
+ import type { AuthenticationScheme } from './AuthenticationScheme.js';
2
+ /**
3
+ * Configuration for {@link cookieScheme}.
4
+ *
5
+ * @template T - The type of the principal value returned by `validate`.
6
+ */
7
+ export interface CookieSchemeOptions<T> {
8
+ /** Name of the cookie to read (e.g. "session", "sid"). */
9
+ cookieName: string;
10
+ /**
11
+ * Validate the cookie value and return the principal data, or `null`
12
+ * if the cookie is invalid / session expired.
13
+ *
14
+ * The implementer is responsible for session lookup, signature
15
+ * verification, etc.
16
+ */
17
+ validate: (cookieValue: string) => Promise<T | null>;
18
+ /** Custom scheme name (default: "cookie"). */
19
+ name?: string;
20
+ }
21
+ /**
22
+ * Create a cookie-based authentication scheme.
23
+ */
24
+ export declare function cookieScheme<T>(options: CookieSchemeOptions<T>): AuthenticationScheme<T>;
@@ -0,0 +1,53 @@
1
+ import type { AuthenticationScheme } from './AuthenticationScheme.js';
2
+ /**
3
+ * The decoded JWT header. Contains the algorithm (`alg`) and optionally
4
+ * the token type (`typ`, typically `"JWT"`).
5
+ */
6
+ export interface JwtHeader {
7
+ alg: string;
8
+ typ?: string;
9
+ }
10
+ /**
11
+ * The decoded JWT payload. Includes standard registered claims (`iss`, `sub`,
12
+ * `aud`, `exp`, `nbf`, `iat`, `jti`) and any custom claims.
13
+ */
14
+ export interface JwtPayload {
15
+ [key: string]: unknown;
16
+ iss?: string;
17
+ sub?: string;
18
+ aud?: string | string[];
19
+ exp?: number;
20
+ nbf?: number;
21
+ iat?: number;
22
+ jti?: string;
23
+ }
24
+ /**
25
+ * Configuration for {@link jwtScheme}.
26
+ *
27
+ * @template T - The type of the principal value produced by `mapClaims`.
28
+ */
29
+ export interface JwtSchemeOptions<T> {
30
+ /** HMAC secret (for HS256/HS384/HS512) or PEM public key (for RS256/RS384/RS512). */
31
+ secret: string | Buffer;
32
+ /** Map raw JWT claims to the typed principal value. */
33
+ mapClaims: (claims: JwtPayload) => T;
34
+ /** Allowed algorithms (default: ['HS256']). */
35
+ algorithms?: string[];
36
+ /** Expected issuer — if set, `iss` must match. */
37
+ issuer?: string;
38
+ /** Expected audience — if set, `aud` must include it. */
39
+ audience?: string;
40
+ /** Clock tolerance in seconds for exp/nbf checks (default: 0). */
41
+ clockTolerance?: number;
42
+ /** Custom scheme name (default: 'jwt'). */
43
+ name?: string;
44
+ }
45
+ /**
46
+ * Create a JWT authentication scheme.
47
+ */
48
+ export declare function jwtScheme<T>(options: JwtSchemeOptions<T>): AuthenticationScheme<T>;
49
+ /**
50
+ * Sign a JWT payload. Utility for tests and token generation.
51
+ * Supports HS256/HS384/HS512 (HMAC) and RS256/RS384/RS512 (RSA).
52
+ */
53
+ export declare function signJwt(payload: JwtPayload, secret: string | Buffer, algorithm?: string): string;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Principal — represents an authenticated (or anonymous) user identity.
3
+ *
4
+ * Generic over `T` — the user-defined principal shape, typically
5
+ * declared as a `@cleverbrush/schema` object schema.
6
+ */
7
+ export declare class Principal<T = unknown> {
8
+ readonly isAuthenticated: boolean;
9
+ readonly value: T | undefined;
10
+ readonly claims: Map<string, string | string[]>;
11
+ constructor(isAuthenticated: boolean, value: T | undefined, claims?: Map<string, string | string[]>);
12
+ /**
13
+ * Returns true if the principal has the given role.
14
+ * Checks the "role" claim by default; supports both single-value
15
+ * and array-value claims.
16
+ */
17
+ hasRole(role: string, claimKey?: string): boolean;
18
+ /**
19
+ * Returns true if the principal has a claim with the given type.
20
+ * If `value` is provided, also checks the claim value matches.
21
+ */
22
+ hasClaim(type: string, value?: string): boolean;
23
+ /**
24
+ * Creates an anonymous (unauthenticated) principal.
25
+ */
26
+ static anonymous(): Principal<undefined>;
27
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Parse a `Cookie` header string into a Record.
3
+ *
4
+ * @example parseCookies('name1=val1; name2=val2') → { name1: 'val1', name2: 'val2' }
5
+ */
6
+ export declare function parseCookies(header: string): Record<string, string>;
7
+ /**
8
+ * Options for the `Set-Cookie` header, controlling cookie lifetime,
9
+ * scope, and security attributes.
10
+ */
11
+ export interface CookieOptions {
12
+ /** Max lifetime in seconds. */
13
+ maxAge?: number;
14
+ /** Absolute expiry date. */
15
+ expires?: Date;
16
+ /** Cookie path (default "/"). */
17
+ path?: string;
18
+ /** Cookie domain. */
19
+ domain?: string;
20
+ /** HTTPS only. */
21
+ secure?: boolean;
22
+ /** Prevent client-side JS access. */
23
+ httpOnly?: boolean;
24
+ /** SameSite attribute. */
25
+ sameSite?: 'Strict' | 'Lax' | 'None';
26
+ }
27
+ /**
28
+ * Serialize a `Set-Cookie` header value.
29
+ */
30
+ export declare function serializeCookie(name: string, value: string, options?: CookieOptions): string;
@@ -0,0 +1,19 @@
1
+ export type { AuthenticationContext, AuthenticationResult, AuthenticationScheme } from './AuthenticationScheme.js';
2
+ export { type AuthorizationPolicy, type AuthorizationRequirement, type AuthorizationResult, AuthorizationService, PolicyBuilder, requireRole } from './Authorization.js';
3
+ export { type CookieSchemeOptions, cookieScheme } from './CookieScheme.js';
4
+ export { type CookieOptions, parseCookies, serializeCookie } from './cookies.js';
5
+ export { type JwtPayload, type JwtSchemeOptions, jwtScheme, signJwt } from './JwtScheme.js';
6
+ export { Principal } from './Principal.js';
7
+ /**
8
+ * Define application roles as a typed constant object.
9
+ * The returned object is frozen and its values form the role
10
+ * string-literal union used by `createEndpoints()`.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const Roles = defineRoles({ admin: 'admin', editor: 'editor' });
15
+ * const ep = createEndpoints(Roles);
16
+ * ep.get('/api/admin').authorize(IPrincipal, 'admin'); // ✓
17
+ * ```
18
+ */
19
+ export declare function defineRoles<const T extends Record<string, string>>(roles: T): Readonly<T>;
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ function R(...i){return e=>i.some(t=>e.hasRole(t))}var m=class{#e=[];requireRole(...e){return this.#e.push(R(...e)),this}require(e){return this.#e.push(e),this}build(e){return{name:e,requirements:[...this.#e]}}},g=class{#e;constructor(e){this.#e=e??new Map}async authorize(e,t){if(!e.isAuthenticated)return{allowed:!1,reason:"Not authenticated"};let r;if(typeof t=="string"){let n=this.#e.get(t);if(!n)return{allowed:!1,reason:`Unknown authorization policy: "${t}"`};r=n.requirements}else r=t;for(let n of r)if(!await n(e))return{allowed:!1,reason:"Forbidden"};return{allowed:!0}}};function y(i){let e={};if(!i)return e;let t=i.split(";");for(let r of t){let n=r.indexOf("=");if(n<0)continue;let a=r.slice(0,n).trim(),o=r.slice(n+1).trim();a.length>0&&(e[a]=decodeURIComponent(o))}return e}function b(i,e,t){let r=`${encodeURIComponent(i)}=${encodeURIComponent(e)}`;return t?.maxAge!==void 0&&(r+=`; Max-Age=${t.maxAge}`),t?.expires&&(r+=`; Expires=${t.expires.toUTCString()}`),t?.path&&(r+=`; Path=${t.path}`),t?.domain&&(r+=`; Domain=${t.domain}`),t?.secure&&(r+="; Secure"),t?.httpOnly&&(r+="; HttpOnly"),t?.sameSite&&(r+=`; SameSite=${t.sameSite}`),r}var h=class i{isAuthenticated;value;claims;constructor(e,t,r){this.isAuthenticated=e,this.value=t,this.claims=r??new Map}hasRole(e,t="role"){let r=this.claims.get(t);return r===void 0?!1:Array.isArray(r)?r.includes(e):r===e}hasClaim(e,t){let r=this.claims.get(e);return r===void 0?!1:t===void 0?!0:Array.isArray(r)?r.includes(t):r===t}static anonymous(){return new i(!1,void 0)}};var A=class{name;#e;constructor(e){this.name=e.name??"cookie",this.#e=e}async authenticate(e){let t=e.cookies[this.#e.cookieName];if(t===void 0){let a=e.headers.cookie;a&&(t=y(a)[this.#e.cookieName])}if(t===void 0||t==="")return{succeeded:!1,failure:`Cookie "${this.#e.cookieName}" not found`};let r=await this.#e.validate(t);return r===null?{succeeded:!1,failure:"Cookie validation failed"}:{succeeded:!0,principal:new h(!0,r)}}};function C(i){return new A(i)}import*as l from"crypto";function x(i){let e=i.replace(/-/g,"+").replace(/_/g,"/"),t=e.length%4;return t===2?e+="==":t===3&&(e+="="),Buffer.from(e,"base64")}var T={HS256:"sha256",HS384:"sha384",HS512:"sha512"},z={RS256:"sha256",RS384:"sha384",RS512:"sha512"};function P(i,e,t,r,n){let a=`${i}.${e}`,o=x(t),c=T[r];if(c){let s=l.createHmac(c,n).update(a).digest();return s.length!==o.length?!1:l.timingSafeEqual(s,o)}let u=z[r];return u?l.createVerify(u).update(a).verify(typeof n=="string"?n:{key:n},o):!1}function J(i,e,t,r,n,a=0){let o=i.split(".");if(o.length!==3)return{error:"Malformed JWT: expected 3 segments"};let[c,u,s]=o,f;try{f=JSON.parse(x(c).toString("utf-8"))}catch{return{error:"Malformed JWT header"}}if(!t.includes(f.alg))return{error:`Unsupported algorithm: ${f.alg}`};if(!P(c,u,s,f.alg,e))return{error:"Invalid signature"};let d;try{d=JSON.parse(x(u).toString("utf-8"))}catch{return{error:"Malformed JWT payload"}}let k=Math.floor(Date.now()/1e3);if(d.exp!==void 0&&k>d.exp+a)return{error:"Token expired"};if(d.nbf!==void 0&&k<d.nbf-a)return{error:"Token not yet valid"};if(r!==void 0&&d.iss!==r)return{error:`Invalid issuer: expected "${r}"`};if(n!==void 0){let p=d.aud;if(!(Array.isArray(p)?p:p?[p]:[]).includes(n))return{error:`Invalid audience: expected "${n}"`}}return{header:f,payload:d}}var w=class{name;#e;#t;constructor(e){this.name=e.name??"jwt",this.#e=e,this.#t=e.algorithms??["HS256"]}async authenticate(e){let t=e.headers.authorization;if(!t)return{succeeded:!1,failure:"Missing Authorization header"};if(!t.startsWith("Bearer "))return{succeeded:!1,failure:"Authorization header must use Bearer scheme"};let r=t.slice(7).trim();if(!r)return{succeeded:!1,failure:"Empty Bearer token"};let n=J(r,this.#e.secret,this.#t,this.#e.issuer,this.#e.audience,this.#e.clockTolerance);if("error"in n)return{succeeded:!1,failure:n.error};let a=this.#e.mapClaims(n.payload),o=new Map;for(let[u,s]of Object.entries(n.payload))(typeof s=="string"||Array.isArray(s)&&s.every(f=>typeof f=="string"))&&o.set(u,s);return{succeeded:!0,principal:new h(!0,a,o)}}challenge(){return{headerName:"WWW-Authenticate",headerValue:"Bearer"}}};function O(i){return new w(i)}function S(i){return i.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function B(i,e,t="HS256"){let r={alg:t,typ:"JWT"},n=S(Buffer.from(JSON.stringify(r),"utf-8")),a=S(Buffer.from(JSON.stringify(i),"utf-8")),o=`${n}.${a}`,c,u=T[t];if(u)c=l.createHmac(u,e).update(o).digest();else{let s=z[t];if(!s)throw new Error(`Unsupported algorithm: ${t}`);c=l.createSign(s).update(o).sign(typeof e=="string"?e:{key:e})}return`${o}.${S(c)}`}function I(i){return Object.freeze(i)}export{g as AuthorizationService,m as PolicyBuilder,h as Principal,C as cookieScheme,I as defineRoles,O as jwtScheme,y as parseCookies,R as requireRole,b as serializeCookie,B as signJwt};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/Authorization.ts","../src/cookies.ts","../src/Principal.ts","../src/CookieScheme.ts","../src/JwtScheme.ts","../src/index.ts"],"sourcesContent":["import type { Principal } from './Principal.js';\n\n// ---------------------------------------------------------------------------\n// Authorization Requirement\n// ---------------------------------------------------------------------------\n\n/**\n * A predicate that a principal must satisfy.\n * Return `true` to allow, `false` to deny.\n */\nexport type AuthorizationRequirement = (\n principal: Principal<any>\n) => boolean | Promise<boolean>;\n\n// ---------------------------------------------------------------------------\n// Roles Requirement\n// ---------------------------------------------------------------------------\n\n/**\n * Creates an authorization requirement that checks if the principal\n * has at least one of the specified roles.\n */\nexport function requireRole(...roles: string[]): AuthorizationRequirement {\n return (principal: Principal<any>) =>\n roles.some(role => principal.hasRole(role));\n}\n\n// ---------------------------------------------------------------------------\n// Authorization Policy\n// ---------------------------------------------------------------------------\n\n/**\n * A named set of authorization requirements.\n * All requirements must pass (AND semantics) for the policy to be satisfied.\n * Build instances using {@link PolicyBuilder}.\n */\nexport interface AuthorizationPolicy {\n readonly name: string;\n readonly requirements: readonly AuthorizationRequirement[];\n}\n\n// ---------------------------------------------------------------------------\n// Policy Builder (fluent API for useAuthorization config)\n// ---------------------------------------------------------------------------\n\n/**\n * Fluent builder for composing {@link AuthorizationPolicy} instances.\n *\n * @example\n * ```ts\n * const policy = new PolicyBuilder()\n * .requireRole('admin')\n * .require(p => p.hasClaim('verified', 'true'))\n * .build('admin-only');\n * ```\n */\nexport class PolicyBuilder {\n readonly #requirements: AuthorizationRequirement[] = [];\n\n /** Add a role requirement: at least one of `roles` must be held by the principal. */\n requireRole(...roles: string[]): this {\n this.#requirements.push(requireRole(...roles));\n return this;\n }\n\n /** Add an arbitrary predicate requirement. */\n require(requirement: AuthorizationRequirement): this {\n this.#requirements.push(requirement);\n return this;\n }\n\n /** Finalize the policy with the given name. */\n build(name: string): AuthorizationPolicy {\n return { name, requirements: [...this.#requirements] };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Authorization Result\n// ---------------------------------------------------------------------------\n\n/**\n * The result of an authorization check performed by {@link AuthorizationService.authorize}.\n */\nexport type AuthorizationResult =\n | { allowed: true }\n | { allowed: false; reason: string };\n\n// ---------------------------------------------------------------------------\n// Authorization Service\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluates authorization requirements or named policies against a principal.\n *\n * Instantiated internally by `ServerBuilder.useAuthorization()` or directly\n * for use outside the server framework.\n */\nexport class AuthorizationService {\n readonly #policies: Map<string, AuthorizationPolicy>;\n\n constructor(policies?: Map<string, AuthorizationPolicy>) {\n this.#policies = policies ?? new Map();\n }\n\n /**\n * Evaluate authorization for a principal.\n *\n * - `requirements` array: all must pass (AND semantics).\n * - Named policy string: looks up registered policy, then checks its requirements.\n */\n async authorize(\n principal: Principal<any>,\n requirements: readonly AuthorizationRequirement[]\n ): Promise<AuthorizationResult>;\n async authorize(\n principal: Principal<any>,\n policyName: string\n ): Promise<AuthorizationResult>;\n async authorize(\n principal: Principal<any>,\n requirementsOrPolicy: readonly AuthorizationRequirement[] | string\n ): Promise<AuthorizationResult> {\n if (!principal.isAuthenticated) {\n return { allowed: false, reason: 'Not authenticated' };\n }\n\n let requirements: readonly AuthorizationRequirement[];\n\n if (typeof requirementsOrPolicy === 'string') {\n const policy = this.#policies.get(requirementsOrPolicy);\n if (!policy) {\n return {\n allowed: false,\n reason: `Unknown authorization policy: \"${requirementsOrPolicy}\"`\n };\n }\n requirements = policy.requirements;\n } else {\n requirements = requirementsOrPolicy;\n }\n\n for (const req of requirements) {\n const result = await req(principal);\n if (!result) {\n return { allowed: false, reason: 'Forbidden' };\n }\n }\n\n return { allowed: true };\n }\n}\n","// ---------------------------------------------------------------------------\n// Cookie Parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a `Cookie` header string into a Record.\n *\n * @example parseCookies('name1=val1; name2=val2') → { name1: 'val1', name2: 'val2' }\n */\nexport function parseCookies(header: string): Record<string, string> {\n const cookies: Record<string, string> = {};\n if (!header) return cookies;\n\n const pairs = header.split(';');\n for (const pair of pairs) {\n const idx = pair.indexOf('=');\n if (idx < 0) continue;\n const key = pair.slice(0, idx).trim();\n const value = pair.slice(idx + 1).trim();\n if (key.length > 0) {\n cookies[key] = decodeURIComponent(value);\n }\n }\n return cookies;\n}\n\n// ---------------------------------------------------------------------------\n// Cookie Serialization\n// ---------------------------------------------------------------------------\n\n/**\n * Options for the `Set-Cookie` header, controlling cookie lifetime,\n * scope, and security attributes.\n */\nexport interface CookieOptions {\n /** Max lifetime in seconds. */\n maxAge?: number;\n /** Absolute expiry date. */\n expires?: Date;\n /** Cookie path (default \"/\"). */\n path?: string;\n /** Cookie domain. */\n domain?: string;\n /** HTTPS only. */\n secure?: boolean;\n /** Prevent client-side JS access. */\n httpOnly?: boolean;\n /** SameSite attribute. */\n sameSite?: 'Strict' | 'Lax' | 'None';\n}\n\n/**\n * Serialize a `Set-Cookie` header value.\n */\nexport function serializeCookie(\n name: string,\n value: string,\n options?: CookieOptions\n): string {\n let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;\n\n if (options?.maxAge !== undefined) {\n cookie += `; Max-Age=${options.maxAge}`;\n }\n if (options?.expires) {\n cookie += `; Expires=${options.expires.toUTCString()}`;\n }\n if (options?.path) {\n cookie += `; Path=${options.path}`;\n }\n if (options?.domain) {\n cookie += `; Domain=${options.domain}`;\n }\n if (options?.secure) {\n cookie += '; Secure';\n }\n if (options?.httpOnly) {\n cookie += '; HttpOnly';\n }\n if (options?.sameSite) {\n cookie += `; SameSite=${options.sameSite}`;\n }\n\n return cookie;\n}\n","/**\n * Principal — represents an authenticated (or anonymous) user identity.\n *\n * Generic over `T` — the user-defined principal shape, typically\n * declared as a `@cleverbrush/schema` object schema.\n */\nexport class Principal<T = unknown> {\n readonly isAuthenticated: boolean;\n readonly value: T | undefined;\n readonly claims: Map<string, string | string[]>;\n\n constructor(\n isAuthenticated: boolean,\n value: T | undefined,\n claims?: Map<string, string | string[]>\n ) {\n this.isAuthenticated = isAuthenticated;\n this.value = value;\n this.claims = claims ?? new Map();\n }\n\n /**\n * Returns true if the principal has the given role.\n * Checks the \"role\" claim by default; supports both single-value\n * and array-value claims.\n */\n hasRole(role: string, claimKey = 'role'): boolean {\n const v = this.claims.get(claimKey);\n if (v === undefined) return false;\n if (Array.isArray(v)) return v.includes(role);\n return v === role;\n }\n\n /**\n * Returns true if the principal has a claim with the given type.\n * If `value` is provided, also checks the claim value matches.\n */\n hasClaim(type: string, value?: string): boolean {\n const v = this.claims.get(type);\n if (v === undefined) return false;\n if (value === undefined) return true;\n if (Array.isArray(v)) return v.includes(value);\n return v === value;\n }\n\n /**\n * Creates an anonymous (unauthenticated) principal.\n */\n static anonymous(): Principal<undefined> {\n return new Principal(false, undefined);\n }\n}\n","import type {\n AuthenticationContext,\n AuthenticationResult,\n AuthenticationScheme\n} from './AuthenticationScheme.js';\nimport { parseCookies } from './cookies.js';\nimport { Principal } from './Principal.js';\n\n// ---------------------------------------------------------------------------\n// Cookie Scheme Config\n// ---------------------------------------------------------------------------\n\n/**\n * Configuration for {@link cookieScheme}.\n *\n * @template T - The type of the principal value returned by `validate`.\n */\nexport interface CookieSchemeOptions<T> {\n /** Name of the cookie to read (e.g. \"session\", \"sid\"). */\n cookieName: string;\n /**\n * Validate the cookie value and return the principal data, or `null`\n * if the cookie is invalid / session expired.\n *\n * The implementer is responsible for session lookup, signature\n * verification, etc.\n */\n validate: (cookieValue: string) => Promise<T | null>;\n /** Custom scheme name (default: \"cookie\"). */\n name?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Cookie Authentication Scheme\n// ---------------------------------------------------------------------------\n\nclass CookieAuthenticationScheme<T> implements AuthenticationScheme<T> {\n readonly name: string;\n readonly #options: CookieSchemeOptions<T>;\n\n constructor(options: CookieSchemeOptions<T>) {\n this.name = options.name ?? 'cookie';\n this.#options = options;\n }\n\n async authenticate(\n context: AuthenticationContext\n ): Promise<AuthenticationResult<T>> {\n // Try pre-parsed cookies first, fall back to header parsing\n let cookieValue: string | undefined =\n context.cookies[this.#options.cookieName];\n\n if (cookieValue === undefined) {\n const cookieHeader = context.headers['cookie'];\n if (cookieHeader) {\n const parsed = parseCookies(cookieHeader);\n cookieValue = parsed[this.#options.cookieName];\n }\n }\n\n if (cookieValue === undefined || cookieValue === '') {\n return {\n succeeded: false,\n failure: `Cookie \"${this.#options.cookieName}\" not found`\n };\n }\n\n const value = await this.#options.validate(cookieValue);\n if (value === null) {\n return {\n succeeded: false,\n failure: 'Cookie validation failed'\n };\n }\n\n const principal = new Principal(true, value);\n return { succeeded: true, principal };\n }\n}\n\n/**\n * Create a cookie-based authentication scheme.\n */\nexport function cookieScheme<T>(\n options: CookieSchemeOptions<T>\n): AuthenticationScheme<T> {\n return new CookieAuthenticationScheme(options);\n}\n","import * as crypto from 'node:crypto';\nimport type {\n AuthenticationContext,\n AuthenticationResult,\n AuthenticationScheme\n} from './AuthenticationScheme.js';\nimport { Principal } from './Principal.js';\n\n// ---------------------------------------------------------------------------\n// JWT Types\n// ---------------------------------------------------------------------------\n\n/**\n * The decoded JWT header. Contains the algorithm (`alg`) and optionally\n * the token type (`typ`, typically `\"JWT\"`).\n */\nexport interface JwtHeader {\n alg: string;\n typ?: string;\n}\n\n/**\n * The decoded JWT payload. Includes standard registered claims (`iss`, `sub`,\n * `aud`, `exp`, `nbf`, `iat`, `jti`) and any custom claims.\n */\nexport interface JwtPayload {\n [key: string]: unknown;\n iss?: string;\n sub?: string;\n aud?: string | string[];\n exp?: number;\n nbf?: number;\n iat?: number;\n jti?: string;\n}\n\n// ---------------------------------------------------------------------------\n// JWT Scheme Config\n// ---------------------------------------------------------------------------\n\n/**\n * Configuration for {@link jwtScheme}.\n *\n * @template T - The type of the principal value produced by `mapClaims`.\n */\nexport interface JwtSchemeOptions<T> {\n /** HMAC secret (for HS256/HS384/HS512) or PEM public key (for RS256/RS384/RS512). */\n secret: string | Buffer;\n /** Map raw JWT claims to the typed principal value. */\n mapClaims: (claims: JwtPayload) => T;\n /** Allowed algorithms (default: ['HS256']). */\n algorithms?: string[];\n /** Expected issuer — if set, `iss` must match. */\n issuer?: string;\n /** Expected audience — if set, `aud` must include it. */\n audience?: string;\n /** Clock tolerance in seconds for exp/nbf checks (default: 0). */\n clockTolerance?: number;\n /** Custom scheme name (default: 'jwt'). */\n name?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Base64url helpers\n// ---------------------------------------------------------------------------\n\nfunction base64urlDecode(input: string): Buffer {\n // Restore standard base64 padding\n let base64 = input.replace(/-/g, '+').replace(/_/g, '/');\n const pad = base64.length % 4;\n if (pad === 2) base64 += '==';\n else if (pad === 3) base64 += '=';\n return Buffer.from(base64, 'base64');\n}\n\n// ---------------------------------------------------------------------------\n// Algorithm mapping\n// ---------------------------------------------------------------------------\n\nconst HMAC_ALGOS: Record<string, string> = {\n HS256: 'sha256',\n HS384: 'sha384',\n HS512: 'sha512'\n};\n\nconst RSA_ALGOS: Record<string, string> = {\n RS256: 'sha256',\n RS384: 'sha384',\n RS512: 'sha512'\n};\n\n// ---------------------------------------------------------------------------\n// JWT verification\n// ---------------------------------------------------------------------------\n\nfunction verifySignature(\n headerB64: string,\n payloadB64: string,\n signatureB64: string,\n alg: string,\n secret: string | Buffer\n): boolean {\n const data = `${headerB64}.${payloadB64}`;\n const signatureBytes = base64urlDecode(signatureB64);\n\n const hmacHash = HMAC_ALGOS[alg];\n if (hmacHash) {\n const expected = crypto\n .createHmac(hmacHash, secret)\n .update(data)\n .digest();\n if (expected.length !== signatureBytes.length) {\n return false;\n }\n return crypto.timingSafeEqual(expected, signatureBytes);\n }\n\n const rsaHash = RSA_ALGOS[alg];\n if (rsaHash) {\n return crypto\n .createVerify(rsaHash)\n .update(data)\n .verify(\n typeof secret === 'string' ? secret : { key: secret },\n signatureBytes\n );\n }\n\n return false;\n}\n\nfunction decodeJwt(\n token: string,\n secret: string | Buffer,\n allowedAlgorithms: string[],\n issuer?: string,\n audience?: string,\n clockTolerance = 0\n): { header: JwtHeader; payload: JwtPayload } | { error: string } {\n const parts = token.split('.');\n if (parts.length !== 3) {\n return { error: 'Malformed JWT: expected 3 segments' };\n }\n\n const [headerB64, payloadB64, signatureB64] = parts;\n\n let header: JwtHeader;\n try {\n header = JSON.parse(base64urlDecode(headerB64).toString('utf-8'));\n } catch {\n return { error: 'Malformed JWT header' };\n }\n\n if (!allowedAlgorithms.includes(header.alg)) {\n return { error: `Unsupported algorithm: ${header.alg}` };\n }\n\n if (\n !verifySignature(\n headerB64,\n payloadB64,\n signatureB64,\n header.alg,\n secret\n )\n ) {\n return { error: 'Invalid signature' };\n }\n\n let payload: JwtPayload;\n try {\n payload = JSON.parse(base64urlDecode(payloadB64).toString('utf-8'));\n } catch {\n return { error: 'Malformed JWT payload' };\n }\n\n const now = Math.floor(Date.now() / 1000);\n\n if (payload.exp !== undefined && now > payload.exp + clockTolerance) {\n return { error: 'Token expired' };\n }\n\n if (payload.nbf !== undefined && now < payload.nbf - clockTolerance) {\n return { error: 'Token not yet valid' };\n }\n\n if (issuer !== undefined && payload.iss !== issuer) {\n return { error: `Invalid issuer: expected \"${issuer}\"` };\n }\n\n if (audience !== undefined) {\n const aud = payload.aud;\n const audArray = Array.isArray(aud) ? aud : aud ? [aud] : [];\n if (!audArray.includes(audience)) {\n return { error: `Invalid audience: expected \"${audience}\"` };\n }\n }\n\n return { header, payload };\n}\n\n// ---------------------------------------------------------------------------\n// JWT Authentication Scheme\n// ---------------------------------------------------------------------------\n\nclass JwtAuthenticationScheme<T> implements AuthenticationScheme<T> {\n readonly name: string;\n readonly #options: JwtSchemeOptions<T>;\n readonly #algorithms: string[];\n\n constructor(options: JwtSchemeOptions<T>) {\n this.name = options.name ?? 'jwt';\n this.#options = options;\n this.#algorithms = options.algorithms ?? ['HS256'];\n }\n\n async authenticate(\n context: AuthenticationContext\n ): Promise<AuthenticationResult<T>> {\n const authHeader = context.headers['authorization'];\n if (!authHeader) {\n return {\n succeeded: false,\n failure: 'Missing Authorization header'\n };\n }\n\n if (!authHeader.startsWith('Bearer ')) {\n return {\n succeeded: false,\n failure: 'Authorization header must use Bearer scheme'\n };\n }\n\n const token = authHeader.slice(7).trim();\n if (!token) {\n return { succeeded: false, failure: 'Empty Bearer token' };\n }\n\n const result = decodeJwt(\n token,\n this.#options.secret,\n this.#algorithms,\n this.#options.issuer,\n this.#options.audience,\n this.#options.clockTolerance\n );\n\n if ('error' in result) {\n return { succeeded: false, failure: result.error };\n }\n\n const value = this.#options.mapClaims(result.payload);\n\n // Build claims map from JWT payload\n const claims = new Map<string, string | string[]>();\n for (const [key, val] of Object.entries(result.payload)) {\n if (typeof val === 'string') {\n claims.set(key, val);\n } else if (\n Array.isArray(val) &&\n val.every(v => typeof v === 'string')\n ) {\n claims.set(key, val);\n }\n }\n\n const principal = new Principal(true, value, claims);\n return { succeeded: true, principal };\n }\n\n challenge(): { headerName: string; headerValue: string } {\n return {\n headerName: 'WWW-Authenticate',\n headerValue: 'Bearer'\n };\n }\n}\n\n/**\n * Create a JWT authentication scheme.\n */\nexport function jwtScheme<T>(\n options: JwtSchemeOptions<T>\n): AuthenticationScheme<T> {\n return new JwtAuthenticationScheme(options);\n}\n\n// ---------------------------------------------------------------------------\n// JWT signing (utility for tests / token generation)\n// ---------------------------------------------------------------------------\n\nfunction base64urlEncode(data: Buffer): string {\n return data\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\n/**\n * Sign a JWT payload. Utility for tests and token generation.\n * Supports HS256/HS384/HS512 (HMAC) and RS256/RS384/RS512 (RSA).\n */\nexport function signJwt(\n payload: JwtPayload,\n secret: string | Buffer,\n algorithm = 'HS256'\n): string {\n const header: JwtHeader = { alg: algorithm, typ: 'JWT' };\n\n const headerB64 = base64urlEncode(\n Buffer.from(JSON.stringify(header), 'utf-8')\n );\n const payloadB64 = base64urlEncode(\n Buffer.from(JSON.stringify(payload), 'utf-8')\n );\n\n const data = `${headerB64}.${payloadB64}`;\n\n let signature: Buffer;\n\n const hmacHash = HMAC_ALGOS[algorithm];\n if (hmacHash) {\n signature = crypto.createHmac(hmacHash, secret).update(data).digest();\n } else {\n const rsaHash = RSA_ALGOS[algorithm];\n if (!rsaHash) {\n throw new Error(`Unsupported algorithm: ${algorithm}`);\n }\n signature = crypto\n .createSign(rsaHash)\n .update(data)\n .sign(typeof secret === 'string' ? secret : { key: secret });\n }\n\n return `${data}.${base64urlEncode(signature)}`;\n}\n","// Core\n\nexport type {\n AuthenticationContext,\n AuthenticationResult,\n AuthenticationScheme\n} from './AuthenticationScheme.js';\nexport {\n type AuthorizationPolicy,\n type AuthorizationRequirement,\n type AuthorizationResult,\n AuthorizationService,\n PolicyBuilder,\n requireRole\n} from './Authorization.js';\nexport { type CookieSchemeOptions, cookieScheme } from './CookieScheme.js';\n\n// Cookie utilities\nexport {\n type CookieOptions,\n parseCookies,\n serializeCookie\n} from './cookies.js';\n\n// Built-in schemes\nexport {\n type JwtPayload,\n type JwtSchemeOptions,\n jwtScheme,\n signJwt\n} from './JwtScheme.js';\nexport { Principal } from './Principal.js';\n\n// Helpers\n\n/**\n * Define application roles as a typed constant object.\n * The returned object is frozen and its values form the role\n * string-literal union used by `createEndpoints()`.\n *\n * @example\n * ```ts\n * const Roles = defineRoles({ admin: 'admin', editor: 'editor' });\n * const ep = createEndpoints(Roles);\n * ep.get('/api/admin').authorize(IPrincipal, 'admin'); // ✓\n * ```\n */\nexport function defineRoles<const T extends Record<string, string>>(\n roles: T\n): Readonly<T> {\n return Object.freeze(roles);\n}\n"],"mappings":"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","names":["requireRole","roles","principal","role","PolicyBuilder","#requirements","requirement","name","AuthorizationService","#policies","policies","requirementsOrPolicy","requirements","policy","req","parseCookies","header","cookies","pairs","pair","idx","key","value","serializeCookie","name","options","cookie","Principal","_Principal","isAuthenticated","value","claims","role","claimKey","v","type","CookieAuthenticationScheme","#options","options","context","cookieValue","cookieHeader","parseCookies","value","Principal","cookieScheme","crypto","base64urlDecode","input","base64","pad","HMAC_ALGOS","RSA_ALGOS","verifySignature","headerB64","payloadB64","signatureB64","alg","secret","data","signatureBytes","hmacHash","expected","rsaHash","decodeJwt","token","allowedAlgorithms","issuer","audience","clockTolerance","parts","header","payload","now","aud","JwtAuthenticationScheme","#options","#algorithms","options","context","authHeader","result","value","claims","key","val","v","Principal","jwtScheme","base64urlEncode","signJwt","algorithm","signature","defineRoles","roles"]}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "author": "Andrew Zolotukhin <andrew_zol@cleverbrush.com>",
3
+ "bugs": {
4
+ "url": "https://github.com/cleverbrush/framework/issues",
5
+ "email": "andrew_zol@cleverbrush.com"
6
+ },
7
+ "dependencies": {
8
+ "@cleverbrush/schema": "0.0.0-beta-20260413195755"
9
+ },
10
+ "description": "Transport-agnostic authentication & authorization — JWT, cookies, role-based policies, schema-typed principals",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "homepage": "https://docs.cleverbrush.com/auth",
15
+ "keywords": [
16
+ "authentication",
17
+ "authorization",
18
+ "jwt",
19
+ "cookies",
20
+ "roles",
21
+ "typescript",
22
+ "cleverbrush"
23
+ ],
24
+ "license": "BSD 3-Clause",
25
+ "main": "./dist/index.js",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js"
30
+ }
31
+ },
32
+ "sideEffects": false,
33
+ "name": "@cleverbrush/auth",
34
+ "readme": "https://github.com/cleverbrush/framework/tree/master/libs/auth#readme",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "github:cleverbrush/framework"
38
+ },
39
+ "scripts": {
40
+ "watch": "tsc --build tsconfig.build.json --watch",
41
+ "build": "tsup && tsc --project tsconfig.build.json --emitDeclarationOnly",
42
+ "clean": "rm -rf dist tsconfig.build.tsbuildinfo"
43
+ },
44
+ "type": "module",
45
+ "types": "./dist/index.d.ts",
46
+ "version": "0.0.0-beta-20260413195755"
47
+ }