alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
1,292 lines • 45 kB
TypeScript
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. */
[]: 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?: {
[]: 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. */
[]: 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;
[]: 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;
[]: 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;
[]: 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