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.
597 lines (594 loc) • 19 kB
TypeScript
import * as _alepha_core2 from "alepha";
import * as _alepha_core3 from "alepha";
import * as _alepha_core1 from "alepha";
import * as _alepha_core0 from "alepha";
import { Alepha, Descriptor, KIND, Static } from "alepha";
import { DateTimeProvider, Duration, DurationLike } from "alepha/datetime";
import { CryptoKey, FlattenedJWSInput, JSONWebKeySet, JWSHeaderParameters, JWTHeaderParameters, JWTPayload, JWTVerifyResult, KeyObject } from "jose";
import * as _sinclair_typebox0 from "@sinclair/typebox";
import * as _sinclair_typebox13 from "@sinclair/typebox";
import * as _sinclair_typebox23 from "@sinclair/typebox";
import { JWTVerifyOptions } from "jose/jwt/verify";
//#region src/schemas/userAccountInfoSchema.d.ts
declare const userAccountInfoSchema: _sinclair_typebox0.TObject<{
id: _sinclair_typebox0.TString;
name: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
email: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
picture: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
organizations: _sinclair_typebox0.TOptional<_sinclair_typebox0.TArray<_sinclair_typebox0.TString>>;
roles: _sinclair_typebox0.TOptional<_sinclair_typebox0.TArray<_sinclair_typebox0.TString>>;
}>;
type UserAccountInfo = Static<typeof userAccountInfoSchema>;
//# sourceMappingURL=userAccountInfoSchema.d.ts.map
//#endregion
//#region src/interfaces/UserAccountToken.d.ts
interface UserAccountToken extends UserAccountInfo {
/**
* Access token for the user.
*/
token?: string;
realm?: string;
/**
* Is user dedicated to his own resources for this scope ?
* Mostly, Admin is false and Customer is true.
*/
ownership?: string | boolean;
}
//# sourceMappingURL=UserAccountToken.d.ts.map
//#endregion
//#region src/schemas/permissionSchema.d.ts
declare const permissionSchema: _sinclair_typebox13.TObject<{
name: _sinclair_typebox13.TString;
group: _sinclair_typebox13.TOptional<_sinclair_typebox13.TString>;
description: _sinclair_typebox13.TOptional<_sinclair_typebox13.TString>;
method: _sinclair_typebox13.TOptional<_sinclair_typebox13.TString>;
path: _sinclair_typebox13.TOptional<_sinclair_typebox13.TString>;
}>;
type Permission = Static<typeof permissionSchema>;
//# sourceMappingURL=permissionSchema.d.ts.map
//#endregion
//#region src/schemas/roleSchema.d.ts
declare const roleSchema: _sinclair_typebox23.TObject<{
name: _sinclair_typebox23.TString;
description: _sinclair_typebox23.TOptional<_sinclair_typebox23.TString>;
default: _sinclair_typebox23.TOptional<_sinclair_typebox23.TBoolean>;
permissions: _sinclair_typebox23.TArray<_sinclair_typebox23.TObject<{
name: _sinclair_typebox23.TString;
ownership: _sinclair_typebox23.TOptional<_sinclair_typebox23.TBoolean>;
exclude: _sinclair_typebox23.TOptional<_sinclair_typebox23.TArray<_sinclair_typebox23.TString>>;
}>>;
}>;
type Role = Static<typeof roleSchema>;
//# sourceMappingURL=roleSchema.d.ts.map
//#endregion
//#region src/providers/JwtProvider.d.ts
/**
* Provides utilities for working with JSON Web Tokens (JWT).
*/
declare class JwtProvider {
protected readonly log: _alepha_core2.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 {
name?: string;
roles?: string[];
email?: string;
organizations?: string[];
realm_access?: {
roles: string[];
};
}
interface JwtParseResult {
keyName: string;
result: JWTVerifyResult<ExtendedJWTPayload>;
}
//# sourceMappingURL=JwtProvider.d.ts.map
//#endregion
//#region src/providers/SecurityProvider.d.ts
declare const envSchema: _alepha_core3.TObject<{
SECURITY_SECRET_KEY: _alepha_core3.TString;
}>;
declare module "alepha" {
interface Env extends Partial<Static<typeof envSchema>> {}
}
declare class SecurityProvider {
protected readonly UNKNOWN_USER_NAME = "Unknown User";
protected readonly PERMISSION_REGEXP: RegExp;
protected readonly PERMISSION_REGEXP_WILDCARD: RegExp;
protected readonly log: _alepha_core3.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 configure: _alepha_core3.HookDescriptor<"start">;
protected ready: _alepha_core3.HookDescriptor<"ready">;
/**
* 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): UserAccountInfo;
/**
* 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;
/**
* 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;
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);
/**
* Attach a user provider to the realm.
*
* This is useful when you want to use a custom user provider for a specific realm.
*/
userAccountProvider?: SecurityUserAccountProvider;
onLoadUser?: (user: UserAccountInfo) => Promise<void> | void;
/**
* Function to create a user profile from the raw JWT user data.
*/
profile?: (raw: Record<string, any>) => UserAccountInfo;
}
interface SecurityUserAccountProvider {
jwks: string | undefined;
synchronize(config: RealmConfig): Promise<void>;
}
interface SecurityCheckResult {
isAuthorized: boolean;
ownership: string | boolean | undefined;
}
interface RealmConfig {
roles?: Array<Role>;
smtp?: {
host?: string;
};
}
//#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: UserAccountInfo): boolean;
}
//# sourceMappingURL=$permission.d.ts.map
//#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>) => UserAccountInfo;
} & (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;
/**
* If true, no refresh token will be created.
*/
disabled?: boolean;
create?: (user: UserAccountInfo, refreshToken?: string) => Promise<{
refresh_token: string;
expires_in: number;
}>;
};
}
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;
/**
* Attach a user account provider to the realm to manage roles.
*
* For example, you can use a KeycloakUserProvider to automatically create/update realm roles inside Keycloak.
*/
userAccountProvider?: SecurityUserAccountProvider | (() => SecurityUserAccountProvider);
}
declare class RealmDescriptor extends Descriptor<RealmDescriptorOptions> {
protected readonly securityProvider: SecurityProvider;
protected readonly dateTimeProvider: DateTimeProvider;
protected readonly jwt: JwtProvider;
protected readonly log: _alepha_core1.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: UserAccountInfo, refreshToken?: string): Promise<AccessTokenResponse>;
}
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;
}
//# sourceMappingURL=$realm.d.ts.map
//#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;
}
//# sourceMappingURL=$role.d.ts.map
//#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: UserAccountInfo;
});
interface Oauth2ServiceAccountDescriptorOptions {
/**
* Get Token URL.
*/
url: string;
/**
* Client ID.
*/
clientId: string;
/**
* Client Secret.
*/
clientSecret: string;
}
interface ServiceAccountDescriptor {
token: () => Promise<string>;
}
interface ServiceAccountStore {
response?: AccessTokenResponse;
}
//# sourceMappingURL=$serviceAccount.d.ts.map
//#endregion
//#region src/errors/InvalidPermissionError.d.ts
declare class InvalidPermissionError extends Error {
constructor(name: string);
}
//# sourceMappingURL=InvalidPermissionError.d.ts.map
//#endregion
//#region src/errors/SecurityError.d.ts
declare class SecurityError extends Error {
name: string;
readonly status = 403;
}
//# sourceMappingURL=SecurityError.d.ts.map
//#endregion
//#region src/providers/CryptoProvider.d.ts
declare class CryptoProvider {
hashPassword(password: string): Promise<string>;
verifyPassword(password: string, stored: string): Promise<boolean>;
}
//# sourceMappingURL=CryptoProvider.d.ts.map
//#endregion
//#region src/index.d.ts
declare module "alepha" {
interface Hooks {
"security:user:created": {
realm: string;
user: UserAccountInfo;
};
}
}
/**
* 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_core0.Service<_alepha_core0.Module>;
//# sourceMappingURL=index.d.ts.map
//#endregion
export { $permission, $realm, $role, $serviceAccount, AccessTokenResponse, AlephaSecurity, CreateTokenOptions, CryptoProvider, ExtendedJWTPayload, InvalidPermissionError, JwtParseResult, JwtProvider, JwtSignOptions, KeyLoader, KeyLoaderHolder, Oauth2ServiceAccountDescriptorOptions, Permission, PermissionDescriptor, PermissionDescriptorOptions, Realm, RealmConfig, RealmDescriptor, RealmDescriptorOptions, RealmExternal, RealmInternal, RealmSettings, Role, RoleDescriptor, RoleDescriptorOptions, SecurityCheckResult, SecurityError, SecurityProvider, SecurityUserAccountProvider, ServiceAccountDescriptor, ServiceAccountDescriptorOptions, ServiceAccountStore, UserAccountInfo, UserAccountToken, permissionSchema, roleSchema, userAccountInfoSchema };
//# sourceMappingURL=index.d.ts.map