@noony-serverless/core
Version:
A Middy base framework compatible with Firebase and GCP Cloud Functions with TypeScript
301 lines • 10.4 kB
JavaScript
;
/**
* CustomTokenVerificationPort Adapter
*
* Bridges the CustomTokenVerificationPort interface from AuthenticationMiddleware
* to the RouteGuards TokenValidator interface, enabling code reuse and unified
* token validation across the entire Noony Framework.
*
* Key Features:
* - Seamless integration between authentication systems
* - Maintains type safety through generics
* - Supports any token validation implementation (JWT, OAuth, API keys, etc.)
* - Preserves all existing RouteGuards functionality
* - Zero-overhead abstraction with full performance
*
* Benefits:
* - One authentication interface to implement and maintain
* - Consistent patterns across AuthenticationMiddleware and RouteGuards
* - Backward compatibility with existing RouteGuards implementations
* - Simplified setup for authentication workflows
*
* @example
* Bridge a JWT verification port to RouteGuards:
* ```typescript
* import { CustomTokenVerificationPort } from '@/middlewares/authenticationMiddleware';
* import { CustomTokenVerificationPortAdapter } from '@/middlewares/guards/adapters';
*
* // Define your user type
* interface User {
* id: string;
* email: string;
* roles: string[];
* sub: string; // JWT subject
* exp: number; // JWT expiration
* iat: number; // Issued at
* }
*
* // Implement token verification once
* const jwtVerifier: CustomTokenVerificationPort<User> = {
* async verifyToken(token: string): Promise<User> {
* const payload = jwt.verify(token, process.env.JWT_SECRET!) as any;
* return {
* id: payload.sub,
* email: payload.email,
* roles: payload.roles || [],
* sub: payload.sub,
* exp: payload.exp,
* iat: payload.iat
* };
* }
* };
*
* // Create adapter for RouteGuards
* const tokenValidator = new CustomTokenVerificationPortAdapter(
* jwtVerifier,
* {
* userIdExtractor: (user: User) => user.id,
* expirationExtractor: (user: User) => user.exp
* }
* );
*
* // Use with RouteGuards
* await RouteGuards.configure(
* GuardSetup.production(),
* userPermissionSource,
* tokenValidator, // Works seamlessly!
* authConfig
* );
* ```
*
* @example
* API key verification with custom user structure:
* ```typescript
* interface APIKeyUser {
* keyId: string;
* permissions: string[];
* organization: string;
* expiresAt: number;
* isActive: boolean;
* }
*
* const apiKeyVerifier: CustomTokenVerificationPort<APIKeyUser> = {
* async verifyToken(token: string): Promise<APIKeyUser> {
* const keyData = await validateAPIKey(token);
* if (!keyData || !keyData.isActive) {
* throw new Error('Invalid or inactive API key');
* }
* return keyData;
* }
* };
*
* // Adapter with custom extractors
* const apiTokenValidator = new CustomTokenVerificationPortAdapter(
* apiKeyVerifier,
* {
* userIdExtractor: (user: APIKeyUser) => user.keyId,
* expirationExtractor: (user: APIKeyUser) => user.expiresAt,
* additionalValidation: (user: APIKeyUser) => user.isActive
* }
* );
*
* // Seamlessly works with RouteGuards
* await RouteGuards.configure(
* GuardSetup.production(),
* userPermissionSource,
* apiTokenValidator,
* authConfig
* );
* ```
*
* @example
* OAuth token verification:
* ```typescript
* interface OAuthUser {
* sub: string; // OAuth subject
* email: string;
* scope: string[]; // OAuth scopes
* exp: number;
* client_id: string;
* }
*
* const oauthVerifier: CustomTokenVerificationPort<OAuthUser> = {
* async verifyToken(token: string): Promise<OAuthUser> {
* // Validate with OAuth provider
* const response = await fetch(`${OAUTH_INTROSPECT_URL}`, {
* method: 'POST',
* headers: { 'Authorization': `Bearer ${token}` }
* });
*
* const tokenInfo = await response.json();
* if (!tokenInfo.active) {
* throw new Error('Token is not active');
* }
*
* return tokenInfo as OAuthUser;
* }
* };
*
* const oauthTokenValidator = new CustomTokenVerificationPortAdapter(
* oauthVerifier,
* {
* userIdExtractor: (user: OAuthUser) => user.sub,
* expirationExtractor: (user: OAuthUser) => user.exp
* }
* );
* ```
*
* @author Noony Framework Team
* @version 1.0.0
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TokenVerificationAdapterFactory = exports.CustomTokenVerificationPortAdapter = void 0;
/**
* Adapter class that bridges CustomTokenVerificationPort to TokenValidator.
* Enables seamless integration between AuthenticationMiddleware and RouteGuards
* while maintaining full type safety and performance.
*
* @template T - The user type returned by the CustomTokenVerificationPort
*/
class CustomTokenVerificationPortAdapter {
verificationPort;
config;
constructor(verificationPort, config) {
this.verificationPort = verificationPort;
this.config = config;
}
/**
* Validate and decode token using the wrapped CustomTokenVerificationPort.
*
* @param token - JWT token string to validate
* @returns Validation result with decoded user data
*/
async validateToken(token) {
try {
// Use the wrapped verification port to verify the token
const user = await this.verificationPort.verifyToken(token);
// Run additional validation if configured
if (this.config.additionalValidation) {
const additionalValid = await this.config.additionalValidation(user);
if (!additionalValid) {
return {
valid: false,
error: this.config.errorMessage || 'Additional validation failed',
};
}
}
return {
valid: true,
decoded: user,
};
}
catch (error) {
return {
valid: false,
error: this.config.errorMessage ||
(error instanceof Error ? error.message : 'Token validation failed'),
};
}
}
/**
* Extract user ID from the decoded token/user data.
* Uses the configured userIdExtractor function.
*
* @param decoded - Decoded user data from validateToken
* @returns User ID string
*/
extractUserId(decoded) {
return this.config.userIdExtractor(decoded);
}
/**
* Check if the token is expired based on the decoded data.
* Uses the configured expirationExtractor if available.
*
* @param decoded - Decoded user data from validateToken
* @returns true if token is expired, false otherwise
*/
isTokenExpired(decoded) {
if (!this.config.expirationExtractor) {
// If no expiration extractor is configured, assume token is valid
return false;
}
const expirationTime = this.config.expirationExtractor(decoded);
if (!expirationTime) {
// If no expiration time is available, assume token is valid
return false;
}
// Compare with current time (convert to seconds)
const currentTime = Math.floor(Date.now() / 1000);
return expirationTime <= currentTime;
}
}
exports.CustomTokenVerificationPortAdapter = CustomTokenVerificationPortAdapter;
/**
* Helper factory functions for common token verification scenarios.
* Provides pre-configured adapters for standard authentication patterns.
*/
class TokenVerificationAdapterFactory {
/**
* Create adapter for standard JWT tokens with common claims.
* Assumes the user object has 'sub' for user ID and 'exp' for expiration.
*
* @param verificationPort - JWT token verification port
* @returns Configured adapter for JWT tokens
*/
static forJWT(verificationPort) {
return new CustomTokenVerificationPortAdapter(verificationPort, {
userIdExtractor: (user) => user.sub,
expirationExtractor: (user) => user.exp,
});
}
/**
* Create adapter for API key tokens with custom ID and expiration fields.
*
* @param verificationPort - API key verification port
* @param userIdField - Field name for user/key ID (e.g., 'keyId', 'apiKeyId')
* @param expirationField - Optional field name for expiration (e.g., 'expiresAt', 'exp')
* @returns Configured adapter for API key tokens
*/
static forAPIKey(verificationPort, userIdField, expirationField) {
return new CustomTokenVerificationPortAdapter(verificationPort, {
userIdExtractor: (user) => String(user[userIdField]),
expirationExtractor: expirationField
? (user) => user[expirationField]
: undefined,
});
}
/**
* Create adapter for OAuth tokens with standard OAuth claims.
*
* @param verificationPort - OAuth token verification port
* @param additionalScopes - Optional required OAuth scopes
* @returns Configured adapter for OAuth tokens
*/
static forOAuth(verificationPort, additionalScopes) {
return new CustomTokenVerificationPortAdapter(verificationPort, {
userIdExtractor: (user) => user.sub,
expirationExtractor: (user) => user.exp,
additionalValidation: additionalScopes
? (user) => {
if (!user.scope || !Array.isArray(user.scope)) {
return false;
}
return additionalScopes.every((requiredScope) => user.scope.includes(requiredScope));
}
: undefined,
});
}
/**
* Create adapter with custom configuration.
* Use this for non-standard token structures or complex validation logic.
*
* @param verificationPort - Token verification port
* @param config - Custom adapter configuration
* @returns Configured adapter with custom settings
*/
static custom(verificationPort, config) {
return new CustomTokenVerificationPortAdapter(verificationPort, config);
}
}
exports.TokenVerificationAdapterFactory = TokenVerificationAdapterFactory;
//# sourceMappingURL=CustomTokenVerificationPortAdapter.js.map