UNPKG

alepha

Version:

Alepha is a convention-driven TypeScript framework for building robust, end-to-end type-safe applications, from serverless APIs to full-stack React apps.

579 lines (578 loc) 17.8 kB
import * as _alepha_core1 from "alepha"; import { Alepha, Descriptor, KIND, Static } from "alepha"; import * as _alepha_logger0 from "alepha/logger"; import { DateTimeProvider, Duration, DurationLike } from "alepha/datetime"; import { CryptoKey, FlattenedJWSInput, JSONWebKeySet, JWSHeaderParameters, JWTHeaderParameters, JWTPayload, JWTVerifyResult, KeyObject } from "jose"; import * as typebox0 from "typebox"; import { JWTVerifyOptions } from "jose/jwt/verify"; //#region src/schemas/userAccountInfoSchema.d.ts declare const userAccountInfoSchema: typebox0.TObject<{ id: typebox0.TString; name: typebox0.TOptional<typebox0.TString>; email: typebox0.TOptional<typebox0.TString>; username: typebox0.TOptional<typebox0.TString>; picture: typebox0.TOptional<typebox0.TString>; sessionId: typebox0.TOptional<typebox0.TString>; organizations: typebox0.TOptional<typebox0.TArray<typebox0.TString>>; roles: typebox0.TOptional<typebox0.TArray<typebox0.TString>>; }>; type UserAccount = Static<typeof userAccountInfoSchema>; //#endregion //#region src/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/schemas/permissionSchema.d.ts declare const permissionSchema: typebox0.TObject<{ name: typebox0.TString; group: typebox0.TOptional<typebox0.TString>; description: typebox0.TOptional<typebox0.TString>; method: typebox0.TOptional<typebox0.TString>; path: typebox0.TOptional<typebox0.TString>; }>; type Permission = Static<typeof permissionSchema>; //#endregion //#region src/schemas/roleSchema.d.ts declare const roleSchema: typebox0.TObject<{ name: typebox0.TString; description: typebox0.TOptional<typebox0.TString>; default: typebox0.TOptional<typebox0.TBoolean>; permissions: typebox0.TArray<typebox0.TObject<{ name: typebox0.TString; ownership: typebox0.TOptional<typebox0.TBoolean>; exclude: typebox0.TOptional<typebox0.TArray<typebox0.TString>>; }>>; }>; type Role = Static<typeof roleSchema>; //#endregion //#region src/providers/JwtProvider.d.ts /** * Provides utilities for working with JSON Web Tokens (JWT). */ declare class JwtProvider { protected readonly log: _alepha_logger0.Logger; protected readonly keystore: KeyLoaderHolder[]; 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; /** * 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 JwtSignOptions { header?: Partial<JWTHeaderParameters>; } interface ExtendedJWTPayload extends JWTPayload { sid?: string; name?: string; roles?: string[]; email?: string; organizations?: string[]; realm_access?: { roles: string[]; }; } interface JwtParseResult { keyName: string; result: JWTVerifyResult<ExtendedJWTPayload>; } //#endregion //#region src/providers/SecurityProvider.d.ts declare const envSchema: _alepha_core1.TObject<{ SECURITY_SECRET_KEY: _alepha_core1.TString; }>; declare module "alepha" { interface Env extends Partial<Static<typeof envSchema>> {} } declare class SecurityProvider { protected readonly UNKNOWN_USER_NAME = "Anonymous User"; protected readonly PERMISSION_REGEXP: RegExp; protected readonly PERMISSION_REGEXP_WILDCARD: RegExp; protected readonly log: _alepha_logger0.Logger; protected readonly jwt: JwtProvider; protected readonly env: { SECURITY_SECRET_KEY: string; }; protected readonly alepha: Alepha; /** * The permissions configured for the security provider. */ protected readonly permissions: Permission[]; /** * The realms configured for the security provider. */ protected readonly realms: Realm[]; protected start: _alepha_core1.HookDescriptor<"start">; /** * 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; /** * 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. * * @param headerOrToken * @param permissionLike */ 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; getRealms(): Realm[]; /** * 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; getOrganizationsFromPayload(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); /** * 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; } interface SecurityCheckResult { isAuthorized: boolean; ownership: string | boolean | undefined; } //#endregion //#region src/descriptors/$permission.d.ts /** * Create a new permission. */ declare const $permission: { (options?: PermissionDescriptorOptions): PermissionDescriptor; [KIND]: typeof PermissionDescriptor; }; interface PermissionDescriptorOptions { /** * 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 PermissionDescriptor extends Descriptor<PermissionDescriptorOptions> { protected readonly securityProvider: SecurityProvider; get name(): string; get group(): string; protected onInit(): void; /** * Check if the user has the permission. */ can(user: UserAccount): boolean; } //#endregion //#region src/descriptors/$realm.d.ts /** * Create a new realm. */ declare const $realm: { (options: RealmDescriptorOptions): RealmDescriptor; [KIND]: typeof RealmDescriptor; }; type RealmDescriptorOptions = { /** * Define the realm name. * If not provided, it will use the property key. */ name?: string; /** * Short description about the realm. */ description?: string; /** * All roles available in the realm. Role is a string (role name) or a Role object (embedded role). */ roles?: Array<string | Role>; settings?: RealmSettings; /** * Parse the JWT payload to create a user account info. */ profile?: (jwtPayload: Record<string, any>) => UserAccount; } & (RealmInternal | RealmExternal); interface RealmSettings { 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; }) => Promise<{ refreshToken: string; sessionId?: string; }>; onRefreshSession?: (refreshToken: string) => Promise<{ user: UserAccount; expiresIn: number; sessionId?: string; }>; onDeleteSession?: (refreshToken: string) => Promise<void>; } type RealmInternal = { /** * Internal secret to sign JWT tokens and verify them. */ secret: string; }; interface RealmExternal { /** * URL to the JWKS (JSON Web Key Set) to verify JWT tokens from external providers. */ jwks: (() => string) | JSONWebKeySet; } declare class RealmDescriptor extends Descriptor<RealmDescriptorOptions> { protected readonly securityProvider: SecurityProvider; protected readonly dateTimeProvider: DateTimeProvider; protected readonly jwt: JwtProvider; protected readonly log: _alepha_logger0.Logger; get name(): string; get accessTokenExpiration(): Duration; get refreshTokenExpiration(): Duration; protected onInit(): void; /** * Get all roles in the realm. */ getRoles(): Role[]; /** * Set all roles in the realm. */ 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; }): 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/descriptors/$role.d.ts /** * Create a new role. */ declare const $role: { (options?: RoleDescriptorOptions): RoleDescriptor; [KIND]: typeof RoleDescriptor; }; interface RoleDescriptorOptions { /** * Name of the role. */ name?: string; /** * Describe the role. */ description?: string; realm?: string | RealmDescriptor; permissions?: Array<string | { name: string; ownership?: boolean; }>; } declare class RoleDescriptor extends Descriptor<RoleDescriptorOptions> { protected readonly securityProvider: SecurityProvider; get name(): string; protected onInit(): void; /** * Get the realm of the role. */ get realm(): string | RealmDescriptor | undefined; } //#endregion //#region src/descriptors/$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: ServiceAccountDescriptorOptions) => ServiceAccountDescriptor; type ServiceAccountDescriptorOptions = { gracePeriod?: number; } & ({ oauth2: Oauth2ServiceAccountDescriptorOptions; } | { realm: RealmDescriptor; user: UserAccount; }); interface Oauth2ServiceAccountDescriptorOptions { /** * Get Token URL. */ url: string; /** * Client ID. */ clientId: string; /** * Client Secret. */ clientSecret: string; } interface ServiceAccountDescriptor { token: () => Promise<string>; } interface ServiceAccountStore { response?: AccessTokenResponse; } //#endregion //#region src/errors/InvalidPermissionError.d.ts declare class InvalidPermissionError extends Error { constructor(name: string); } //#endregion //#region src/errors/SecurityError.d.ts declare class SecurityError extends Error { name: string; readonly status = 403; } //#endregion //#region src/providers/CryptoProvider.d.ts declare class CryptoProvider { hashPassword(password: string): Promise<string>; verifyPassword(password: string, stored: string): Promise<boolean>; } //#endregion //#region src/index.d.ts declare module "alepha" { interface Hooks { "security:user:created": { realm: string; user: UserAccount; }; } } /** * Provides comprehensive authentication and authorization capabilities with JWT tokens, role-based access control, and user management. * * The security module enables building secure applications using descriptors like `$realm`, `$role`, and `$permission` * on class properties. It offers JWT-based authentication, fine-grained permissions, service accounts, and seamless * integration with various authentication providers and user management systems. * * @see {@link $realm} * @see {@link $role} * @see {@link $permission} * @module alepha.security */ declare const AlephaSecurity: _alepha_core1.Service<_alepha_core1.Module>; //#endregion export { $permission, $realm, $role, $serviceAccount, AccessTokenResponse, AlephaSecurity, CreateTokenOptions, CryptoProvider, ExtendedJWTPayload, InvalidPermissionError, JwtParseResult, JwtProvider, JwtSignOptions, KeyLoader, KeyLoaderHolder, Oauth2ServiceAccountDescriptorOptions, Permission, PermissionDescriptor, PermissionDescriptorOptions, Realm, RealmDescriptor, RealmDescriptorOptions, RealmExternal, RealmInternal, RealmSettings, Role, RoleDescriptor, RoleDescriptorOptions, SecurityCheckResult, SecurityError, SecurityProvider, ServiceAccountDescriptor, ServiceAccountDescriptorOptions, ServiceAccountStore, UserAccount, UserAccountToken, permissionSchema, roleSchema, userAccountInfoSchema }; //# sourceMappingURL=index.d.ts.map