UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

1,292 lines 45 kB
import { Alepha, KIND, Middleware, Primitive, Static } from "alepha"; import { DateTimeProvider, Duration, DurationLike } from "alepha/datetime"; import { CryptoProvider, SecretProvider } from "alepha/crypto"; import { FetchOptions, ServerRequest, UnauthorizedError } from "alepha/server"; export * from "alepha/crypto"; //#region ../../src/security/schemas/userAccountInfoSchema.d.ts declare const userAccountInfoSchema: import("zod").ZodObject<{ id: import("zod").ZodString; name: import("zod").ZodOptional<import("zod").ZodString>; firstName: import("zod").ZodOptional<import("zod").ZodString>; lastName: import("zod").ZodOptional<import("zod").ZodString>; email: import("zod").ZodOptional<import("zod").ZodString>; username: import("zod").ZodOptional<import("zod").ZodString>; picture: import("zod").ZodOptional<import("zod").ZodString>; sessionId: import("zod").ZodOptional<import("zod").ZodString>; organization: import("zod").ZodOptional<import("zod").ZodString>; roles: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>; realm: import("zod").ZodOptional<import("zod").ZodString>; }, import("zod/v4/core").$strip>; type UserAccount = Static<typeof userAccountInfoSchema>; //#endregion //#region ../../src/security/interfaces/UserAccountToken.d.ts /** * Add contextual metadata to a user account info. * E.g. UserAccountToken is a UserAccountInfo during a request. */ interface UserAccountToken extends UserAccount { /** * Access token for the user. */ token?: string; /** * Realm name of the user. */ realm?: string; /** * Is user dedicated to his own resources for this scope ? * Mostly, Admin is false and Customer is true. */ ownership?: string | boolean; } //#endregion //#region ../../src/security/atoms/currentTenantAtom.d.ts /** * Atom storing the active tenant for the current request. * * Transport-agnostic — works with HTTP, MCP, pipelines, jobs, and any context * that sets the atom before calling tenant-scoped logic. * * Typically set by an app-level middleware that resolves the tenant from the * request `Host` header (or another signal) and writes the resolved id to the * store. Framework code that reads this atom: * * - Repository scoping: `withOrganization` / `stampOrganization` prefer this * value over `currentUserAtom.organization` so cross-tenant users (admins, * agency operators) are scoped to the tenant they are currently acting in * rather than the one they belong to. * - Session creation: the value is persisted into the JWT as a `tenant` claim, * and the issuer resolver rejects tokens whose claim does not match the * tenant resolved from the current request. * * `id` is a free-form string so the framework stays neutral on tenant identity * (slug, UUID, composite). Pick whatever matches the column marked with * `PG_ORGANIZATION` in your entities. */ declare const currentTenantAtom: import("alepha").Atom<import("zod").ZodOptional<import("zod").ZodObject<{ id: import("zod").ZodString; }, import("zod/v4/core").$strip>>, "alepha.security.tenant">; //#endregion //#region ../../src/security/atoms/currentUserAtom.d.ts /** * Atom storing the current authenticated user. * * Transport-agnostic — works with HTTP, MCP, pipelines, jobs, and any context * that sets the atom before calling secured logic. */ declare const currentUserAtom: import("alepha").Atom<import("zod").ZodOptional<import("zod").ZodObject<{ id: import("zod").ZodString; name: import("zod").ZodOptional<import("zod").ZodString>; firstName: import("zod").ZodOptional<import("zod").ZodString>; lastName: import("zod").ZodOptional<import("zod").ZodString>; email: import("zod").ZodOptional<import("zod").ZodString>; username: import("zod").ZodOptional<import("zod").ZodString>; picture: import("zod").ZodOptional<import("zod").ZodString>; sessionId: import("zod").ZodOptional<import("zod").ZodString>; organization: import("zod").ZodOptional<import("zod").ZodString>; roles: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>; realm: import("zod").ZodOptional<import("zod").ZodString>; }, import("zod/v4/core").$strip>>, "alepha.security.user">; //#endregion //#region ../../src/security/errors/InvalidCredentialsError.d.ts /** * Error thrown when the provided credentials are invalid. * * Message can not be changed to avoid leaking information. * Cause is omitted for the same reason. */ declare class InvalidCredentialsError extends UnauthorizedError { readonly name = "UnauthorizedError"; constructor(); } //#endregion //#region ../../src/security/errors/InvalidPermissionError.d.ts declare class InvalidPermissionError extends Error { constructor(name: string); } //#endregion //#region ../../src/security/errors/SecurityError.d.ts declare class SecurityError extends Error { name: string; readonly status = 403; } //#endregion //#region ../../src/security/interfaces/IssuerResolver.d.ts /** * User info that a resolver returns. * This is the input to `SecurityProvider.createUser()`. */ type UserInfo = Omit<UserAccount, "sessionId"> & { sessionId?: string; }; /** * Resolver definition for authenticating users from requests. */ interface IssuerResolver { /** * Priority (lower = first). Default: 100 */ priority?: number; /** * Resolve user from HTTP request. * Return UserInfo if authenticated, null to try next resolver. * Throw UnauthorizedError to stop chain. */ onRequest: (req: ServerRequest) => Promise<UserInfo | null>; } //#endregion //#region ../../src/security/primitives/$basicAuth.d.ts interface BasicAuthOptions { username: string; password: string; } /** * Middleware that enforces HTTP Basic Authentication on the request. * * Works with request context only (HTTP). Reads the `Authorization: Basic` header, * validates credentials using timing-safe comparison, and throws 401 if invalid. * * ```typescript * class DevToolsController { * dashboard = $action({ * use: [$basicAuth({ username: "admin", password: "secret" })], * handler: async () => { ... }, * }); * } * ``` */ declare function $basicAuth(options: BasicAuthOptions): Middleware; //#endregion //#region ../../../../node_modules/jose/dist/types/types.d.ts /** Generic JSON Web Key Parameters. */ interface JWKParameters { /** JWK "kty" (Key Type) Parameter */ kty?: string; /** * JWK "alg" (Algorithm) Parameter * * @see {@link https://github.com/panva/jose/issues/210 Algorithm Key Requirements} */ alg?: string; /** JWK "key_ops" (Key Operations) Parameter */ key_ops?: string[]; /** JWK "ext" (Extractable) Parameter */ ext?: boolean; /** JWK "use" (Public Key Use) Parameter */ use?: string; /** JWK "x5c" (X.509 Certificate Chain) Parameter */ x5c?: string[]; /** JWK "x5t" (X.509 Certificate SHA-1 Thumbprint) Parameter */ x5t?: string; /** JWK "x5t#S256" (X.509 Certificate SHA-256 Thumbprint) Parameter */ 'x5t#S256'?: string; /** JWK "x5u" (X.509 URL) Parameter */ x5u?: string; /** JWK "kid" (Key ID) Parameter */ kid?: string; } /** * JSON Web Key ({@link https://www.rfc-editor.org/rfc/rfc7517 JWK}). "RSA", "EC", "OKP", "AKP", and * "oct" key types are supported. * * @see {@link JWK_AKP_Public} * @see {@link JWK_AKP_Private} * @see {@link JWK_OKP_Public} * @see {@link JWK_OKP_Private} * @see {@link JWK_EC_Public} * @see {@link JWK_EC_Private} * @see {@link JWK_RSA_Public} * @see {@link JWK_RSA_Private} * @see {@link JWK_oct} */ interface JWK extends JWKParameters { /** * - EC JWK "crv" (Curve) Parameter * - OKP JWK "crv" (The Subtype of Key Pair) Parameter */ crv?: string; /** * - Private RSA JWK "d" (Private Exponent) Parameter * - Private EC JWK "d" (ECC Private Key) Parameter * - Private OKP JWK "d" (The Private Key) Parameter */ d?: string; /** Private RSA JWK "dp" (First Factor CRT Exponent) Parameter */ dp?: string; /** Private RSA JWK "dq" (Second Factor CRT Exponent) Parameter */ dq?: string; /** RSA JWK "e" (Exponent) Parameter */ e?: string; /** Oct JWK "k" (Key Value) Parameter */ k?: string; /** RSA JWK "n" (Modulus) Parameter */ n?: string; /** Private RSA JWK "p" (First Prime Factor) Parameter */ p?: string; /** Private RSA JWK "q" (Second Prime Factor) Parameter */ q?: string; /** Private RSA JWK "qi" (First CRT Coefficient) Parameter */ qi?: string; /** * - EC JWK "x" (X Coordinate) Parameter * - OKP JWK "x" (The public key) Parameter */ x?: string; /** EC JWK "y" (Y Coordinate) Parameter */ y?: string; /** AKP JWK "pub" (Public Key) Parameter */ pub?: string; /** AKP JWK "priv" (Private key) Parameter */ priv?: string; } /** * Flattened JWS definition for verify function inputs, allows payload as {@link !Uint8Array} for * detached signature validation. */ interface FlattenedJWSInput { /** * The "header" member MUST be present and contain the value JWS Unprotected Header when the JWS * Unprotected Header value is non- empty; otherwise, it MUST be absent. This value is represented * as an unencoded JSON object, rather than as a string. These Header Parameter values are not * integrity protected. */ header?: JWSHeaderParameters; /** * The "payload" member MUST be present and contain the value BASE64URL(JWS Payload). When RFC7797 * "b64": false is used the value passed may also be a {@link !Uint8Array}. */ payload: string | Uint8Array; /** * The "protected" member MUST be present and contain the value BASE64URL(UTF8(JWS Protected * Header)) when the JWS Protected Header value is non-empty; otherwise, it MUST be absent. These * Header Parameter values are integrity protected. */ protected?: string; /** The "signature" member MUST be present and contain the value BASE64URL(JWS Signature). */ signature: string; } /** Header Parameters common to JWE and JWS */ interface JoseHeaderParameters { /** "kid" (Key ID) Header Parameter */ kid?: string; /** "x5t" (X.509 Certificate SHA-1 Thumbprint) Header Parameter */ x5t?: string; /** "x5c" (X.509 Certificate Chain) Header Parameter */ x5c?: string[]; /** "x5u" (X.509 URL) Header Parameter */ x5u?: string; /** "jku" (JWK Set URL) Header Parameter */ jku?: string; /** "jwk" (JSON Web Key) Header Parameter */ jwk?: Pick<JWK, 'kty' | 'crv' | 'x' | 'y' | 'e' | 'n' | 'alg' | 'pub'>; /** "typ" (Type) Header Parameter */ typ?: string; /** "cty" (Content Type) Header Parameter */ cty?: string; } /** Recognized JWS Header Parameters, any other Header Members may also be present. */ interface JWSHeaderParameters extends JoseHeaderParameters { /** * JWS "alg" (Algorithm) Header Parameter * * @see {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements} */ alg?: string; /** * This JWS Extension Header Parameter modifies the JWS Payload representation and the JWS Signing * Input computation as per {@link https://www.rfc-editor.org/rfc/rfc7797 RFC7797}. */ b64?: boolean; /** JWS "crit" (Critical) Header Parameter */ crit?: string[]; /** Any other JWS Header member. */ [propName: string]: unknown; } /** Shared Interface with a "crit" property for all sign, verify, encrypt and decrypt operations. */ interface CritOption { /** * An object with keys representing recognized "crit" (Critical) Header Parameter names. The value * for those is either `true` or `false`. `true` when the Header Parameter MUST be integrity * protected, `false` when it's irrelevant. * * This makes the "Extension Header Parameter "..." is not recognized" error go away. * * Use this when a given JWS/JWT/JWE profile requires the use of proprietary non-registered "crit" * (Critical) Header Parameters. This will only make sure the Header Parameter is syntactically * correct when provided and that it is optionally integrity protected. It will not process the * Header Parameter in any way or reject the operation if it is missing. You MUST still verify the * Header Parameter was present and process it according to the profile's validation steps after * the operation succeeds. * * The JWS extension Header Parameter `b64` is always recognized and processed properly. No other * registered Header Parameters that need this kind of default built-in treatment are currently * available. */ crit?: { [propName: string]: boolean; }; } /** JWT Claims Set verification options. */ interface JWTClaimVerificationOptions { /** * Expected JWT "aud" (Audience) Claim value(s). * * This option makes the JWT "aud" (Audience) Claim presence required. */ audience?: string | string[]; /** * Clock skew tolerance * * - In seconds when number (e.g. 5) * - Resolved into a number of seconds when a string (e.g. "5 seconds", "10 minutes", "2 hours"). * * Used when validating the JWT "nbf" (Not Before) and "exp" (Expiration Time) claims, and when * validating the "iat" (Issued At) claim if the {@link maxTokenAge `maxTokenAge` option} is set. */ clockTolerance?: string | number; /** * Expected JWT "iss" (Issuer) Claim value(s). * * This option makes the JWT "iss" (Issuer) Claim presence required. */ issuer?: string | string[]; /** * Maximum time elapsed (in seconds) from the JWT "iat" (Issued At) Claim value. * * - In seconds when number (e.g. 5) * - Resolved into a number of seconds when a string (e.g. "5 seconds", "10 minutes", "2 hours"). * * This option makes the JWT "iat" (Issued At) Claim presence required. */ maxTokenAge?: string | number; /** * Expected JWT "sub" (Subject) Claim value. * * This option makes the JWT "sub" (Subject) Claim presence required. */ subject?: string; /** * Expected JWT "typ" (Type) Header Parameter value. * * This option makes the JWT "typ" (Type) Header Parameter presence required. */ typ?: string; /** Date to use when comparing NumericDate claims, defaults to `new Date()`. */ currentDate?: Date; /** * Array of required Claim Names that must be present in the JWT Claims Set. Default is that: if * the {@link issuer `issuer` option} is set, then JWT "iss" (Issuer) Claim must be present; if the * {@link audience `audience` option} is set, then JWT "aud" (Audience) Claim must be present; if * the {@link subject `subject` option} is set, then JWT "sub" (Subject) Claim must be present; if * the {@link maxTokenAge `maxTokenAge` option} is set, then JWT "iat" (Issued At) Claim must be * present. */ requiredClaims?: string[]; } /** JWS Verification options. */ interface VerifyOptions extends CritOption { /** * A list of accepted JWS "alg" (Algorithm) Header Parameter values. By default all "alg" * (Algorithm) values applicable for the used key/secret are allowed. * * > [!NOTE]\ * > Unsecured JWTs (`{ "alg": "none" }`) are never accepted by this API. */ algorithms?: string[]; } /** Recognized JWT Claims Set members, any other members may also be present. */ interface JWTPayload { /** * JWT Issuer * * @see {@link https://www.rfc-editor.org/rfc/rfc7519#section-4.1.1 RFC7519#section-4.1.1} */ iss?: string; /** * JWT Subject * * @see {@link https://www.rfc-editor.org/rfc/rfc7519#section-4.1.2 RFC7519#section-4.1.2} */ sub?: string; /** * JWT Audience * * @see {@link https://www.rfc-editor.org/rfc/rfc7519#section-4.1.3 RFC7519#section-4.1.3} */ aud?: string | string[]; /** * JWT ID * * @see {@link https://www.rfc-editor.org/rfc/rfc7519#section-4.1.7 RFC7519#section-4.1.7} */ jti?: string; /** * JWT Not Before * * @see {@link https://www.rfc-editor.org/rfc/rfc7519#section-4.1.5 RFC7519#section-4.1.5} */ nbf?: number; /** * JWT Expiration Time * * @see {@link https://www.rfc-editor.org/rfc/rfc7519#section-4.1.4 RFC7519#section-4.1.4} */ exp?: number; /** * JWT Issued At * * @see {@link https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6 RFC7519#section-4.1.6} */ iat?: number; /** Any other JWT Claim Set member. */ [propName: string]: unknown; } /** Signed JSON Web Token (JWT) verification result */ interface JWTVerifyResult<PayloadType = JWTPayload> { /** JWT Claims Set. */ payload: PayloadType & JWTPayload; /** JWS Protected Header. */ protectedHeader: JWTHeaderParameters; } /** Recognized Compact JWS Header Parameters, any other Header Members may also be present. */ interface CompactJWSHeaderParameters extends JWSHeaderParameters { alg: string; } /** Recognized Signed JWT Header Parameters, any other Header Members may also be present. */ interface JWTHeaderParameters extends CompactJWSHeaderParameters { b64?: true; } /** JSON Web Key Set */ interface JSONWebKeySet { keys: JWK[]; } /** * {@link !KeyObject} is a representation of a key/secret available in the Node.js runtime. You may * use the Node.js runtime APIs {@link !createPublicKey}, {@link !createPrivateKey}, and * {@link !createSecretKey} to obtain a {@link !KeyObject} from your existing key material. */ interface KeyObject { type: string; } /** * {@link !CryptoKey} is a representation of a key/secret available in all supported runtimes. In * addition to the {@link key/import Key Import Functions} you may use the * {@link !SubtleCrypto.importKey} API to obtain a {@link !CryptoKey} from your existing key * material. */ type CryptoKey = Extract<Awaited<ReturnType<typeof crypto.subtle.generateKey>>, { type: string; }>; //#endregion //#region ../../../../node_modules/jose/dist/types/jwt/verify.d.ts /** Combination of JWS Verification options and JWT Claims Set verification options. */ interface JWTVerifyOptions extends VerifyOptions, JWTClaimVerificationOptions {} //#endregion //#region ../../src/security/providers/JwtProvider.d.ts /** * Provides utilities for working with JSON Web Tokens (JWT). */ declare class JwtProvider { protected readonly log: import("alepha/logger").Logger; protected readonly keystore: KeyLoaderHolder[]; protected readonly signers: Map<string, SignerHolder>; protected readonly dateTimeProvider: DateTimeProvider; protected readonly encoder: TextEncoder; /** * Adds a key loader to the embedded keystore. * * @param name * @param secretKeyOrJwks */ setKeyLoader(name: string, secretKeyOrJwks: string | JSONWebKeySet): void; /** * Configure an asymmetric signing key for a realm/issuer. Tokens minted for * `name` are then signed with `alg` + `kid`, and a local verify loader is * registered so this process can verify its own tokens (refresh tokens, * authorization codes, id_tokens). Public keys are published via `getJwks`. * * `privateKey` is a PKCS#8 PEM (from env). When it is empty/undefined an * **ephemeral per-process keypair** is generated so dev/test work with no env * key configured — the published JWKS still resolves; tokens just don't * survive a restart. Additive: realms without a signing key keep the HS256 * path untouched. */ setSigningKey(name: string, signing: SigningConfig): Promise<void>; /** * Public JWK Set for a realm/issuer configured with an asymmetric signing * key. Returns an empty set for HS256 / external (URL-based) realms. */ getJwks(name: string): Promise<JSONWebKeySet>; /** * Retrieves the payload from a JSON Web Token (JWT). * * @param token - The JWT to extract the payload from. * * @return A Promise that resolves with the payload object from the token. */ parse(token: string, keyName?: string, options?: JWTVerifyOptions): Promise<JwtParseResult>; /** * Creates a JWT token with the provided payload and secret key. * * @param payload - The payload to be encoded in the token. * It should include the `realm_access` property which contains an array of roles. * @param keyName - The name of the key to use when signing the token. * * @returns The signed JWT token. */ create(payload: ExtendedJWTPayload, keyName?: string, signOptions?: JwtSignOptions): Promise<string>; /** * Determines if the provided key is a secret key. * * @param key * @protected */ protected isSecretKey(key: string): boolean; } type KeyLoader = (protectedHeader?: JWSHeaderParameters, token?: FlattenedJWSInput) => Promise<CryptoKey | KeyObject>; interface KeyLoaderHolder { name: string; keyLoader: KeyLoader; secretKey?: string; } interface SignerHolder { alg: string; kid: string; key: CryptoKey | KeyObject; publicJwk: JWK; } /** * Asymmetric signing config for a realm/issuer. `privateKey` is a PKCS#8 PEM * (typically injected from env). EdDSA preferred; RS256 acceptable. */ interface SigningConfig { alg: "EdDSA" | "RS256"; /** * PKCS#8 PEM private key. Empty/undefined → an ephemeral per-process keypair * is generated (dev/test convenience; tokens don't survive a restart). */ privateKey?: string; kid: string; } interface JwtSignOptions { header?: Partial<JWTHeaderParameters>; } interface ExtendedJWTPayload extends JWTPayload { sid?: string; name?: string; roles?: string[]; email?: string; organization?: string; realm_access?: { roles: string[]; }; } interface JwtParseResult { keyName: string; result: JWTVerifyResult<ExtendedJWTPayload>; } //#endregion //#region ../../src/security/schemas/permissionSchema.d.ts declare const permissionSchema: import("zod").ZodObject<{ name: import("zod").ZodString; group: import("zod").ZodOptional<import("zod").ZodString>; description: import("zod").ZodOptional<import("zod").ZodString>; method: import("zod").ZodOptional<import("zod").ZodString>; path: import("zod").ZodOptional<import("zod").ZodString>; }, import("zod/v4/core").$strip>; type Permission = Static<typeof permissionSchema>; //#endregion //#region ../../src/security/schemas/roleSchema.d.ts declare const roleSchema: import("zod").ZodObject<{ name: import("zod").ZodString; description: import("zod").ZodOptional<import("zod").ZodString>; default: import("zod").ZodOptional<import("zod").ZodBoolean>; permissions: import("zod").ZodArray<import("zod").ZodObject<{ name: import("zod").ZodString; ownership: import("zod").ZodOptional<import("zod").ZodBoolean>; exclude: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>; }, import("zod/v4/core").$strip>>; }, import("zod/v4/core").$strip>; type Role = Static<typeof roleSchema>; //#endregion //#region ../../src/security/providers/SecurityProvider.d.ts declare class SecurityProvider { protected readonly UNKNOWN_USER_NAME = "Anonymous User"; protected readonly PERMISSION_REGEXP: RegExp; protected readonly PERMISSION_REGEXP_WILDCARD: RegExp; protected readonly log: import("alepha/logger").Logger; protected readonly jwt: JwtProvider; protected readonly alepha: Alepha; protected readonly secretProvider: SecretProvider; get secretKey(): string; /** * The permissions configured for the security provider. */ protected readonly permissions: Permission[]; /** * The realms configured for the security provider. */ protected readonly realms: Realm[]; protected start: import("alepha").HookPrimitive<"start">; /** * Creates a default JWT resolver for a realm. */ protected createDefaultJwtResolver(realmName: string): IssuerResolver; /** * Adds a role to one or more realms. * * @param role * @param realms */ createRole(role: Role, ...realms: string[]): Role; /** * Adds a permission to the security provider. * * @param raw - The permission to add. */ createPermission(raw: Permission | string): Permission; createRealm(realm: Realm): void; /** * Updates the roles for a realm then synchronizes the user account provider if available. * * Only available when the app is started. * * @param realm - The realm to update the roles for. * @param roles - The roles to update. */ updateRealm(realm: string, roles: Role[]): Promise<void>; /** * Creates a user account from the provided payload. * * @param payload - The payload to create the user account from. * @param [realmName] - The realm containing the roles. Default is all. * * @returns The user info created from the payload. */ createUserFromPayload(payload: JWTPayload, realmName?: string): UserAccount; /** * Generic user creation from any source (JWT, API key, etc.). * Handles permission checking, ownership, default roles. */ createUser(userInfo: UserInfo, options?: { realm?: string; permission?: Permission | string; }): UserAccountToken; /** * Register a resolver to a realm. * Resolvers are sorted by priority (lower = first). */ registerResolver(resolver: IssuerResolver, realmName?: string): void; /** * Get a realm by name. * Throws if realm not found. */ getRealm(realmName?: string): Realm; /** * Resolve user from request using registered resolvers. * Returns undefined if no resolver could authenticate (no auth provided). * Throws UnauthorizedError if auth was provided but invalid. * * Note: This method tries resolvers from ALL realms to find a match, * regardless of the `realm` option. The `realm` option is only used for * permission checking after the user is resolved. */ resolveUserFromServerRequest(req: { url: URL | string; headers: { authorization?: string; }; }, options?: { realm?: string; permission?: Permission | string; }): Promise<UserAccountToken | undefined>; /** * Checks if the user has the specified permission. * * Bonus: we check also if the user has "ownership" flag. * * @param permissionLike - The permission to check for. * @param roleEntries - The roles to check for the permission. */ checkPermission(permissionLike: string | Permission, ...roleEntries: string[]): SecurityCheckResult; /** * Creates a user account from the provided payload. */ createUserFromToken(headerOrToken?: string, options?: { permission?: Permission | string; realm?: string; verify?: JWTVerifyOptions; }): Promise<UserAccountToken>; /** * Checks if a user has a specific role. * * @param roleName - The role to check for. * @param permission - The permission to check for. * @returns True if the user has the role, false otherwise. */ can(roleName: string, permission: string | Permission): boolean; /** * Checks if a user has ownership of a specific permission. */ ownership(roleName: string, permission: string | Permission): string | boolean | undefined; /** * Converts a permission object to a string. * * @param permission */ permissionToString(permission: Permission | string): string; /** * Check that the user belongs to one of the required issuers. */ checkIssuers(user: UserAccountToken, issuers?: string[]): void; /** * Store the resolved user in the atom for downstream access (useAuth, audit trail, etc.). */ storeUserInContext(user: UserAccountToken): void; getRealms(): Realm[]; /** * Resolve the realm an authenticated admin action is allowed to operate on. * * Admin endpoints accept a client-supplied target realm (e.g. a * `userRealmName` query param). Without scoping, a realm-A admin could act on * realm-B resources by simply naming realm B — a cross-tenant escalation * whenever realms are used as tenant boundaries. This binds the target realm * to the caller's own realm: omitting the target means "my realm"; naming a * *different* realm than the caller's is refused. * * Returns the effective realm to use (may be `undefined` in single-realm apps * where neither the caller nor the request carries a realm). */ assertRealmScope(user: { realm?: string; } | undefined, requestedRealm: string | undefined): string | undefined; /** * Retrieves the user account from the provided user ID. * * @param realm */ getRoles(realm?: string): Role[]; /** * Returns all permissions. * * @param user - Filter permissions by user. * * @return An array containing all permissions. */ getPermissions(user?: { roles?: Array<Role | string>; realm?: string; }): Permission[]; /** * Retrieves the user ID from the provided payload object. * * @param payload - The payload object from which to extract the user ID. * @return The user ID as a string. */ getIdFromPayload(payload: Record<string, any>): string; getSessionIdFromPayload(payload: Record<string, any>): string | undefined; /** * Retrieves the roles from the provided payload object. * @param payload - The payload object from which to extract the roles. * @return An array of role strings. */ getRolesFromPayload(payload: Record<string, any>): string[]; getPictureFromPayload(payload: Record<string, any>): string | undefined; getUsernameFromPayload(payload: Record<string, any>): string | undefined; getEmailFromPayload(payload: Record<string, any>): string | undefined; /** * Returns the name from the given payload. * * @param payload - The payload object. * @returns The name extracted from the payload, or an empty string if the payload is falsy or no name is found. */ getNameFromPayload(payload: Record<string, any>): string; getOrganizationFromPayload(payload: Record<string, any>): string | undefined; /** * Extracts the tenant id from the JWT payload, when present. * * Tokens minted with no active tenant (single-tenant apps, server-to-server * calls before any request-scoped middleware runs) omit the claim, in which * case the resolver does not enforce a tenant match. */ getTenantFromPayload(payload: Record<string, any>): string | undefined; } /** * A realm definition. */ interface Realm { name: string; roles: Role[]; /** * The secret key for the realm. * * Can be also a JWKS URL. */ secret?: string | JSONWebKeySet | (() => string); /** * Asymmetric signing config. When set, this realm signs its tokens with an * asymmetric key and publishes the public keys via JWKS (see * `JwtProvider.getJwks`). Takes precedence over `secret`. */ signing?: SigningConfig; /** * Create the user account info based on the raw JWT payload. * By default, SecurityProvider has his own implementation, but this method allow to override it. */ profile?: (raw: Record<string, any>) => UserAccount; /** * Custom resolvers for this realm (sorted by priority). */ resolvers?: IssuerResolver[]; } interface SecurityCheckResult { isAuthorized: boolean; ownership: string | boolean | undefined; } //#endregion //#region ../../src/security/primitives/$issuer.d.ts /** * Create a new issuer. * * An issuer is responsible for creating and verifying JWT tokens. * It can be internal (with a secret) or external (with a JWKS). */ declare const $issuer: { (options: IssuerPrimitiveOptions): IssuerPrimitive; [KIND]: typeof IssuerPrimitive; }; type IssuerPrimitiveOptions = { /** * Define the issuer name. * If not provided, it will use the property key. */ name?: string; /** * Short description about the issuer. */ description?: string; /** * All roles available in the issuer. Role is a string (role name) or a Role object (embedded role). */ roles?: Array<string | Role>; /** * Issuer settings. */ settings?: IssuerSettings; /** * Parse the JWT payload to create a user account info. */ profile?: (jwtPayload: Record<string, any>) => UserAccount; /** * Custom resolvers (in addition to default JWT resolver). */ resolvers?: IssuerResolver[]; /** * Asymmetric signing config. When set, this issuer's tokens are signed with * the given alg + kid and its public keys are published via JWKS. When * omitted, the HS256 `secret` path is used (default, backward compatible). */ signing?: SigningConfig; } & (IssuerInternal | IssuerExternal); interface IssuerSettings { accessToken?: { /** * Lifetime of the access token. * @default 15 minutes */ expiration?: DurationLike; }; refreshToken?: { /** * Lifetime of the refresh token. * @default 30 days */ expiration?: DurationLike; }; onCreateSession?: (user: UserAccount, config: { expiresIn: number; /** * OAuth client id, when the session is being created for an OAuth * 2.1 authorization-code grant. Lets the session store record which * MCP client / app the session belongs to. */ clientId?: string; }) => Promise<{ refreshToken: string; sessionId?: string; }>; onRefreshSession?: (refreshToken: string) => Promise<{ user: UserAccount; expiresIn: number; sessionId?: string; }>; onDeleteSession?: (refreshToken: string) => Promise<void>; } type IssuerInternal = { /** * Internal secret to sign JWT tokens and verify them. */ secret: string; }; interface IssuerExternal { /** * URL to the JWKS (JSON Web Key Set) to verify JWT tokens from external providers. */ jwks: (() => string) | JSONWebKeySet; } declare class IssuerPrimitive extends Primitive<IssuerPrimitiveOptions> { protected readonly alepha: Alepha; protected readonly securityProvider: SecurityProvider; protected readonly dateTimeProvider: DateTimeProvider; protected readonly jwt: JwtProvider; protected readonly log: import("alepha/logger").Logger; get name(): string; get accessTokenExpiration(): Duration; get refreshTokenExpiration(): Duration; protected onInit(): void; /** * Creates the default JWT resolver. */ protected createJwtResolver(): IssuerResolver; /** * Register a resolver to this issuer. * Resolvers are sorted by priority (lower = first). */ registerResolver(resolver: IssuerResolver): void; /** * Get all roles in the issuer. */ getRoles(): Role[]; /** * Set all roles in the issuer. */ setRoles(roles: Role[]): Promise<void>; /** * Get a role by name, throws an error if not found. */ getRoleByName(name: string): Role; parseToken(token: string): Promise<JWTPayload>; /** * Create a token for the subject. */ createToken(user: UserAccount, refreshToken?: { sid?: string; refresh_token?: string; refresh_token_expires_in?: number; }, context?: { /** * OAuth client id to tag a freshly created session with. Only used * on the `onCreateSession` path (no `refreshToken` passed). */ clientId?: string; }): Promise<AccessTokenResponse>; refreshToken(refreshToken: string, accessToken?: string): Promise<{ tokens: AccessTokenResponse; user: UserAccount; }>; } interface CreateTokenOptions { sub: string; roles?: string[]; email?: string; } interface AccessTokenResponse { access_token: string; token_type: string; expires_in?: number; issued_at: number; refresh_token?: string; refresh_token_expires_in?: number; scope?: string; } //#endregion //#region ../../src/security/primitives/$permission.d.ts /** * Create a new permission. */ declare const $permission: { (options?: PermissionPrimitiveOptions): PermissionPrimitive; [KIND]: typeof PermissionPrimitive; }; interface PermissionPrimitiveOptions { /** * Name of the permission. Use Property name is not provided. */ name?: string; /** * Group of the permission. Use Class name is not provided. */ group?: string; /** * Describe the permission. */ description?: string; } declare class PermissionPrimitive extends Primitive<PermissionPrimitiveOptions> { protected readonly securityProvider: SecurityProvider; get name(): string; get group(): string; toString(): string; protected onInit(): void; /** * Check if the user has the permission. */ can(user?: UserAccount): boolean; } //#endregion //#region ../../src/security/primitives/$role.d.ts /** * Create a new role. */ declare const $role: { (options?: RolePrimitiveOptions): RolePrimitive; [KIND]: typeof RolePrimitive; }; interface RolePrimitiveOptions { /** * Name of the role. */ name?: string; /** * Describe the role. */ description?: string; issuer?: string | IssuerPrimitive; permissions?: Array<string | { name: string; ownership?: boolean; exclude?: string[]; }>; } declare class RolePrimitive extends Primitive<RolePrimitiveOptions> { protected readonly securityProvider: SecurityProvider; get name(): string; protected onInit(): void; /** * Get the issuer of the role. */ get issuer(): string | IssuerPrimitive | undefined; can(permission: string | PermissionPrimitive): boolean; check(permission: string | PermissionPrimitive): SecurityCheckResult; } //#endregion //#region ../../src/security/primitives/$secure.d.ts interface SecureOptions { /** * Restrict to specific issuers (realms). * User must belong to one of the listed issuers. */ issuers?: string[]; /** * Required roles. User must have at least one of the listed roles. */ roles?: string[]; /** * Required permissions. All must be satisfied. */ permissions?: (string | Permission)[]; /** * Custom guard function. Runs after all other checks. * Return `false` to deny access. */ guard?: (user: UserAccountToken) => boolean; } /** * Middleware that enforces authentication and authorization. * * Resolves the user from the request context, `currentUserAtom`, or authorization headers. * Throws `UnauthorizedError` if no user is resolved, `ForbiddenError` if checks fail. * Stores the resolved user in `currentUserAtom` and `request.user` for downstream access. * * Works across all transports (atom-first resolution): * 1. `currentUserAtom` — set by `action.run()` fork, MCP transport, pipelines, jobs * 2. `request.user` — set by previous middleware * 3. HTTP headers — JWT/API key resolution * * ## Check Order * * When multiple options are provided, they are checked in this fixed order. * All provided options must pass (AND). Each option has its own logic: * * 1. **Authentication** — Is there a valid user? → `UnauthorizedError` (401) * 2. **Issuers** (OR) — Does the user's realm match at least one? → `ForbiddenError` (403) * 3. **Roles** (OR) — Does the user have at least one of the listed roles? → `ForbiddenError` (403) * 4. **Permissions** (AND) — Does the user's role grant all listed permissions? → `ForbiddenError` (403) * 5. **Guard** — Does the custom function return `true`? → `ForbiddenError` (403) * * Permissions declared in `$secure()` are auto-created in the permission registry at definition time. * * ## Browser Behavior * * On the server, `$secure` throws `UnauthorizedError` or `ForbiddenError`. * In the browser, it returns `undefined` instead — the handler is never called. * * ```typescript * class OrderController { * getOrders = $action({ * use: [$secure()], * handler: async ({ query }) => { ... }, * }); * * deleteOrder = $action({ * use: [$secure({ permissions: ["orders:delete"] })], * handler: async ({ params }) => { ... }, * }); * * adminManage = $action({ * use: [$secure({ * issuers: ["main"], * roles: ["admin"], * permissions: ["admin:manage"], * guard: (user) => !!user.email, * })], * handler: () => { ... }, * }); * } * ``` */ declare function $secure(options?: SecureOptions): Middleware; //#endregion //#region ../../src/security/primitives/$serviceAccount.d.ts /** * Allow to get an access token for a service account. * * You have some options to configure the service account: * - a OAUTH2 URL using client credentials grant type * - a JWT secret shared between the services * * @example * ```ts * import { $serviceAccount } from "alepha/security"; * * class MyService { * serviceAccount = $serviceAccount({ * oauth2: { * url: "https://example.com/oauth2/token", * clientId: "your-client-id", * clientSecret: "your-client-secret", * } * }); * * async fetchData() { * const token = await this.serviceAccount.token(); * // or * const response = await this.serviceAccount.fetch("https://api.example.com/data"); * } * } * ``` */ declare const $serviceAccount: (options: ServiceAccountPrimitiveOptions) => ServiceAccountPrimitive; type ServiceAccountPrimitiveOptions = { gracePeriod?: number; } & ({ oauth2: Oauth2ServiceAccountPrimitiveOptions; } | { issuer: IssuerPrimitive; user: UserAccount; }); interface Oauth2ServiceAccountPrimitiveOptions { /** * Get Token URL. */ url: string; /** * Client ID. */ clientId: string; /** * Client Secret. */ clientSecret: string; } interface ServiceAccountPrimitive { token: () => Promise<string>; } interface ServiceAccountStore { response?: AccessTokenResponse; } //#endregion //#region ../../src/security/providers/ServerSecurityProvider.d.ts declare class ServerSecurityProvider { protected readonly log: import("alepha/logger").Logger; protected readonly securityProvider: SecurityProvider; protected readonly jwtProvider: JwtProvider; protected readonly alepha: Alepha; protected readonly crypto: CryptoProvider; protected readonly onServerRequest: import("alepha").HookPrimitive<"server:onRequest">; protected readonly onActionRequest: import("alepha").HookPrimitive<"action:onRequest">; protected createTestUser(): UserAccountToken; protected readonly onClientRequest: import("alepha").HookPrimitive<"client:onRequest">; } type ServerSecurityUserResolver = (request: ServerRequest) => Promise<UserAccountToken | undefined>; //#endregion //#region ../../src/security/index.d.ts declare module "alepha" { interface Hooks { "security:user:created": { realm: string; user: UserAccount; }; } interface State { /** * Real (or fake) user account, used for internal actions. * * If you define this, you assume that all actions are executed by this user by default. * > To force a different user, you need to pass it explicitly in the options. */ "alepha.security.system.user"?: UserAccountToken; /** * The current authenticated user. */ "alepha.security.user"?: UserAccount; /** * The tenant the current request is acting in. * * Typically set by an app-level middleware from the request `Host`. When * present, `Repository` scoping and session creation prefer this value * over `currentUserAtom.organization`. */ "alepha.security.tenant"?: { id: string; }; } } declare module "alepha/server" { interface ServerRequest<TConfig> { user?: UserAccountToken; } interface ServerActionRequest<TConfig> { user: UserAccountToken; } interface ClientRequestOptions extends FetchOptions { /** * Forward user from the previous request. * If "system", use system user. @see {ServerSecurityProvider.localSystemUser} * If "context", use the user from the current context (e.g. request). * * @default "system" if provided, else "context" if available. */ user?: UserAccountToken | "system" | "context"; } } /** * Complete authentication and authorization system with JWT, RBAC, and multi-issuer support. * * **Features:** * - JWT token issuer with role definitions * - Role-based access control (RBAC) * - Fine-grained permissions * - HTTP Basic Authentication * - Service-to-service authentication * - Multi-issuer support for federated auth * - JWKS (JSON Web Key Set) for external issuers * - Token refresh logic * - User profile extraction from JWT * * @module alepha.security */ declare const AlephaSecurity: import("alepha").Service<import("alepha").Module>; //#endregion export { $basicAuth, $issuer, $permission, $role, $secure, $serviceAccount, AccessTokenResponse, AlephaSecurity, BasicAuthOptions, CreateTokenOptions, ExtendedJWTPayload, InvalidCredentialsError, InvalidPermissionError, IssuerExternal, IssuerInternal, IssuerPrimitive, IssuerPrimitiveOptions, IssuerResolver, IssuerSettings, JwtParseResult, JwtProvider, JwtSignOptions, KeyLoader, KeyLoaderHolder, Oauth2ServiceAccountPrimitiveOptions, Permission, PermissionPrimitive, PermissionPrimitiveOptions, Realm, Role, RolePrimitive, RolePrimitiveOptions, SecureOptions, SecurityCheckResult, SecurityError, SecurityProvider, ServerSecurityProvider, ServerSecurityUserResolver, ServiceAccountPrimitive, ServiceAccountPrimitiveOptions, ServiceAccountStore, SignerHolder, SigningConfig, UserAccount, UserAccountToken, UserInfo, currentTenantAtom, currentUserAtom, permissionSchema, roleSchema, userAccountInfoSchema }; //# sourceMappingURL=index.d.ts.map