First commit
Some checks failed
armco-org/iam-server-sdk/pipeline/head There was a failure building this commit
Some checks failed
armco-org/iam-server-sdk/pipeline/head There was a failure building this commit
This commit is contained in:
7
Jenkinsfile
vendored
Normal file
7
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
@Library('jenkins-shared') _
|
||||
|
||||
kanikoPipeline(
|
||||
repoName: 'iam-server-sdk',
|
||||
branch: env.BRANCH_NAME ?: 'main',
|
||||
isNpmLib: true
|
||||
)
|
||||
143
README.md
Normal file
143
README.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# @armco/iam-server
|
||||
|
||||
Server-side JWT validation and middleware for IAM.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @armco/iam-server
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Standalone Verifier
|
||||
|
||||
```typescript
|
||||
import { createIAMVerifier } from '@armco/iam-server';
|
||||
|
||||
const verifier = createIAMVerifier({
|
||||
issuer: 'http://localhost:5000',
|
||||
audience: 'my-api',
|
||||
});
|
||||
|
||||
// Verify a token
|
||||
const result = await verifier.verify(token);
|
||||
if (result.valid) {
|
||||
console.log('User ID:', result.payload.sub);
|
||||
console.log('Email:', result.payload.email);
|
||||
console.log('Roles:', result.payload.roles);
|
||||
}
|
||||
|
||||
// Or authenticate and get structured user info
|
||||
const user = await verifier.authenticate(token);
|
||||
if (user) {
|
||||
console.log(user.id, user.email, user.roles, user.scopes);
|
||||
}
|
||||
```
|
||||
|
||||
### Express Middleware
|
||||
|
||||
```typescript
|
||||
import express from 'express';
|
||||
import { createAuthMiddleware, requireRole } from '@armco/iam-server/express';
|
||||
|
||||
const app = express();
|
||||
|
||||
// Create auth middleware
|
||||
const auth = createAuthMiddleware({
|
||||
issuer: 'http://localhost:5000',
|
||||
audience: 'my-api',
|
||||
});
|
||||
|
||||
// Protect all /api routes
|
||||
app.use('/api', auth());
|
||||
|
||||
// Access user in handlers
|
||||
app.get('/api/profile', (req, res) => {
|
||||
res.json({
|
||||
id: req.user.id,
|
||||
email: req.user.email,
|
||||
roles: req.user.roles,
|
||||
});
|
||||
});
|
||||
|
||||
// Require specific role
|
||||
app.get('/api/admin', auth({ roles: ['admin'] }), (req, res) => {
|
||||
res.json({ message: 'Admin access granted' });
|
||||
});
|
||||
|
||||
// Require specific scope
|
||||
app.get('/api/data', auth({ scopes: ['read:data'] }), (req, res) => {
|
||||
res.json({ data: '...' });
|
||||
});
|
||||
|
||||
// Or use standalone middleware
|
||||
app.delete('/api/users/:id', auth(), requireRole('admin'), handler);
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```typescript
|
||||
interface IAMServerConfig {
|
||||
/** IAM server base URL */
|
||||
issuer: string;
|
||||
/** Expected audience (your app's client_id) */
|
||||
audience: string;
|
||||
/** Cache JWKS keys (default: true) */
|
||||
cacheKeys?: boolean;
|
||||
/** JWKS cache TTL in seconds (default: 3600) */
|
||||
cacheTTL?: number;
|
||||
/** Required scopes for all requests */
|
||||
requiredScopes?: string[];
|
||||
/** Custom claim to extract user ID from (default: 'sub') */
|
||||
userIdClaim?: string;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### IAMVerifier
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `verify(token)` | Verify JWT, returns `{ valid, payload?, error? }` |
|
||||
| `authenticate(token)` | Verify and return `AuthenticatedUser` or `null` |
|
||||
| `hasRole(user, roles)` | Check if user has any of the roles |
|
||||
| `hasAllRoles(user, roles)` | Check if user has all roles |
|
||||
| `hasScope(user, scopes)` | Check if user has any of the scopes |
|
||||
| `hasAllScopes(user, scopes)` | Check if user has all scopes |
|
||||
| `clearCache()` | Force JWKS refresh on next verify |
|
||||
|
||||
### Express Middleware
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `createAuthMiddleware(config)` | Create auth middleware factory |
|
||||
| `auth(options?)` | Middleware that requires valid token |
|
||||
| `requireRole(roles)` | Middleware to check roles (use after auth) |
|
||||
| `requireScope(scopes)` | Middleware to check scopes (use after auth) |
|
||||
| `createOptionalAuthMiddleware(config)` | Attaches user if token present, doesn't require it |
|
||||
|
||||
### AuthenticatedUser
|
||||
|
||||
```typescript
|
||||
interface AuthenticatedUser {
|
||||
id: string; // Global identity ID (from 'sub')
|
||||
userId?: string; // Tenant-specific user ID
|
||||
tenantId?: string; // Tenant ID
|
||||
email?: string;
|
||||
username?: string;
|
||||
roles: string[];
|
||||
scopes: string[];
|
||||
claims: JWTPayload; // Raw JWT payload
|
||||
}
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
cd packages/iam-server
|
||||
npm install
|
||||
npm run build
|
||||
npm run dev # Watch mode
|
||||
```
|
||||
60
package.json
Normal file
60
package.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "@armco/iam-server",
|
||||
"version": "0.1.0",
|
||||
"description": "Server-side JWT validation and middleware for IAM",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./express": {
|
||||
"import": "./dist/express.mjs",
|
||||
"require": "./dist/express.js",
|
||||
"types": "./dist/express.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"test": "vitest run",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
"iam",
|
||||
"jwt",
|
||||
"authentication",
|
||||
"middleware",
|
||||
"armco",
|
||||
"express"
|
||||
],
|
||||
"author": "Armco",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"express": ">=4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"express": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"jose": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.10.0",
|
||||
"express": "^4.18.2",
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.3.0",
|
||||
"vitest": "^1.0.0"
|
||||
}
|
||||
}
|
||||
200
src/express/index.ts
Normal file
200
src/express/index.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* Express middleware for IAM authentication
|
||||
*/
|
||||
|
||||
import type { Request, Response, NextFunction } from 'express';
|
||||
import { IAMVerifier, createIAMVerifier } from '../verifier';
|
||||
import type { IAMServerConfig, AuthenticatedUser, AuthOptions } from '../types';
|
||||
|
||||
// Extend Express Request to include user
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: AuthenticatedUser;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract Bearer token from Authorization header
|
||||
*/
|
||||
function extractToken(req: Request): string | null {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader) return null;
|
||||
|
||||
const parts = authHeader.split(' ');
|
||||
if (parts.length !== 2 || parts[0].toLowerCase() !== 'bearer') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create authentication middleware
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { createAuthMiddleware } from '@armco/iam-server/express';
|
||||
*
|
||||
* const auth = createAuthMiddleware({
|
||||
* issuer: 'http://localhost:5000',
|
||||
* audience: 'my-api',
|
||||
* });
|
||||
*
|
||||
* // Protect all routes
|
||||
* app.use('/api', auth());
|
||||
*
|
||||
* // Or specific routes
|
||||
* app.get('/api/users', auth(), (req, res) => {
|
||||
* console.log(req.user); // AuthenticatedUser
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function createAuthMiddleware(config: IAMServerConfig) {
|
||||
const verifier = createIAMVerifier(config);
|
||||
|
||||
/**
|
||||
* Authentication middleware factory
|
||||
*/
|
||||
return function auth(options: AuthOptions = {}) {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const token = extractToken(req);
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
error: 'unauthorized',
|
||||
message: 'Missing or invalid Authorization header',
|
||||
});
|
||||
}
|
||||
|
||||
const user = await verifier.authenticate(token);
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
error: 'unauthorized',
|
||||
message: 'Invalid or expired token',
|
||||
});
|
||||
}
|
||||
|
||||
// Check required scopes
|
||||
if (options.scopes && options.scopes.length > 0) {
|
||||
const hasScope = options.requireAllScopes
|
||||
? verifier.hasAllScopes(user, options.scopes)
|
||||
: verifier.hasScope(user, options.scopes);
|
||||
|
||||
if (!hasScope) {
|
||||
return res.status(403).json({
|
||||
error: 'forbidden',
|
||||
message: `Insufficient scope. Required: ${options.scopes.join(', ')}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check required roles
|
||||
if (options.roles && options.roles.length > 0) {
|
||||
const hasRole = options.requireAllRoles
|
||||
? verifier.hasAllRoles(user, options.roles)
|
||||
: verifier.hasRole(user, options.roles);
|
||||
|
||||
if (!hasRole) {
|
||||
return res.status(403).json({
|
||||
error: 'forbidden',
|
||||
message: `Insufficient permissions. Required role: ${options.roles.join(' or ')}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Attach user to request
|
||||
req.user = user;
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create role-checking middleware (use after auth middleware)
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* app.get('/admin', auth(), requireRole('admin'), handler);
|
||||
* app.get('/editor', auth(), requireRole(['admin', 'editor']), handler);
|
||||
* ```
|
||||
*/
|
||||
export function requireRole(roles: string | string[]) {
|
||||
const required = Array.isArray(roles) ? roles : [roles];
|
||||
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
error: 'unauthorized',
|
||||
message: 'Authentication required',
|
||||
});
|
||||
}
|
||||
|
||||
const hasRole = required.some(role => req.user!.roles.includes(role));
|
||||
if (!hasRole) {
|
||||
return res.status(403).json({
|
||||
error: 'forbidden',
|
||||
message: `Required role: ${required.join(' or ')}`,
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create scope-checking middleware (use after auth middleware)
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* app.get('/data', auth(), requireScope('read:data'), handler);
|
||||
* ```
|
||||
*/
|
||||
export function requireScope(scopes: string | string[]) {
|
||||
const required = Array.isArray(scopes) ? scopes : [scopes];
|
||||
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
error: 'unauthorized',
|
||||
message: 'Authentication required',
|
||||
});
|
||||
}
|
||||
|
||||
const hasScope = required.some(scope => req.user!.scopes.includes(scope));
|
||||
if (!hasScope) {
|
||||
return res.status(403).json({
|
||||
error: 'forbidden',
|
||||
message: `Required scope: ${required.join(' or ')}`,
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional auth - attaches user if token present, but doesn't require it
|
||||
*/
|
||||
export function createOptionalAuthMiddleware(config: IAMServerConfig) {
|
||||
const verifier = createIAMVerifier(config);
|
||||
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const token = extractToken(req);
|
||||
|
||||
if (token) {
|
||||
const user = await verifier.authenticate(token);
|
||||
if (user) {
|
||||
req.user = user;
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
// Re-export types
|
||||
export type { IAMServerConfig, AuthenticatedUser, AuthOptions } from '../types';
|
||||
export { IAMVerifier, createIAMVerifier } from '../verifier';
|
||||
41
src/index.ts
Normal file
41
src/index.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @armco/iam-server
|
||||
*
|
||||
* Server-side JWT validation and utilities for IAM.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { createIAMVerifier } from '@armco/iam-server';
|
||||
*
|
||||
* const verifier = createIAMVerifier({
|
||||
* issuer: 'http://localhost:5000',
|
||||
* audience: 'my-api',
|
||||
* });
|
||||
*
|
||||
* // Verify a token
|
||||
* const result = await verifier.verify(token);
|
||||
* if (result.valid) {
|
||||
* console.log(result.payload);
|
||||
* }
|
||||
*
|
||||
* // Or authenticate and get user info
|
||||
* const user = await verifier.authenticate(token);
|
||||
* if (user) {
|
||||
* console.log(user.email, user.roles);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* For Express middleware, use:
|
||||
* ```ts
|
||||
* import { createAuthMiddleware } from '@armco/iam-server/express';
|
||||
* ```
|
||||
*/
|
||||
|
||||
export { IAMVerifier, createIAMVerifier } from './verifier';
|
||||
export type {
|
||||
IAMServerConfig,
|
||||
JWTPayload,
|
||||
VerifyResult,
|
||||
AuthenticatedUser,
|
||||
AuthOptions,
|
||||
} from './types';
|
||||
83
src/types.ts
Normal file
83
src/types.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @armco/iam-server Types
|
||||
*/
|
||||
|
||||
export interface IAMServerConfig {
|
||||
/** IAM server base URL (e.g., http://localhost:5000) */
|
||||
issuer: string;
|
||||
/** Expected audience (your app's client_id) */
|
||||
audience: string;
|
||||
/** Cache JWKS keys (default: true) */
|
||||
cacheKeys?: boolean;
|
||||
/** JWKS cache TTL in seconds (default: 3600) */
|
||||
cacheTTL?: number;
|
||||
/** Required scopes for all requests (optional) */
|
||||
requiredScopes?: string[];
|
||||
/** Custom claim to extract user ID from (default: 'sub') */
|
||||
userIdClaim?: string;
|
||||
}
|
||||
|
||||
export interface JWTPayload {
|
||||
/** Subject - global identity ID */
|
||||
sub: string;
|
||||
/** Issuer */
|
||||
iss: string;
|
||||
/** Audience */
|
||||
aud: string | string[];
|
||||
/** Issued at */
|
||||
iat: number;
|
||||
/** Expiration */
|
||||
exp: number;
|
||||
/** User's email */
|
||||
email?: string;
|
||||
/** Email verified */
|
||||
email_verified?: boolean;
|
||||
/** Username */
|
||||
username?: string;
|
||||
/** Tenant ID */
|
||||
tenantId?: string;
|
||||
/** User ID (membership ID) */
|
||||
userId?: string;
|
||||
/** Roles */
|
||||
roles?: string[];
|
||||
/** Scopes */
|
||||
scope?: string;
|
||||
/** Additional claims */
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface VerifyResult {
|
||||
valid: boolean;
|
||||
payload?: JWTPayload;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface AuthenticatedUser {
|
||||
/** Global identity ID (from 'sub' claim) */
|
||||
id: string;
|
||||
/** Tenant-specific user ID */
|
||||
userId?: string;
|
||||
/** Tenant ID */
|
||||
tenantId?: string;
|
||||
/** Email */
|
||||
email?: string;
|
||||
/** Username */
|
||||
username?: string;
|
||||
/** Roles */
|
||||
roles: string[];
|
||||
/** Scopes */
|
||||
scopes: string[];
|
||||
/** Raw JWT payload */
|
||||
claims: JWTPayload;
|
||||
}
|
||||
|
||||
export interface AuthOptions {
|
||||
/** Required scopes (any of these) */
|
||||
scopes?: string[];
|
||||
/** Required roles (any of these) */
|
||||
roles?: string[];
|
||||
/** Require all specified scopes (default: false - any match) */
|
||||
requireAllScopes?: boolean;
|
||||
/** Require all specified roles (default: false - any match) */
|
||||
requireAllRoles?: boolean;
|
||||
}
|
||||
154
src/verifier.ts
Normal file
154
src/verifier.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* JWT Verifier using jose library
|
||||
* Fetches JWKS from IAM and validates tokens
|
||||
*/
|
||||
|
||||
import { createRemoteJWKSet, jwtVerify, type JWTVerifyResult } from 'jose';
|
||||
import type { IAMServerConfig, JWTPayload, VerifyResult, AuthenticatedUser } from './types';
|
||||
|
||||
export class IAMVerifier {
|
||||
private config: Required<IAMServerConfig>;
|
||||
private jwks: ReturnType<typeof createRemoteJWKSet> | null = null;
|
||||
private jwksCreatedAt: number = 0;
|
||||
|
||||
constructor(config: IAMServerConfig) {
|
||||
this.config = {
|
||||
issuer: config.issuer.replace(/\/$/, ''),
|
||||
audience: config.audience,
|
||||
cacheKeys: config.cacheKeys ?? true,
|
||||
cacheTTL: config.cacheTTL ?? 3600,
|
||||
requiredScopes: config.requiredScopes ?? [],
|
||||
userIdClaim: config.userIdClaim ?? 'sub',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create JWKS
|
||||
*/
|
||||
private getJWKS(): ReturnType<typeof createRemoteJWKSet> {
|
||||
const now = Date.now();
|
||||
const ttlMs = this.config.cacheTTL * 1000;
|
||||
|
||||
// Return cached JWKS if still valid
|
||||
if (this.jwks && this.config.cacheKeys && (now - this.jwksCreatedAt) < ttlMs) {
|
||||
return this.jwks;
|
||||
}
|
||||
|
||||
// Create new JWKS
|
||||
const jwksUrl = new URL(`${this.config.issuer}/.well-known/jwks.json`);
|
||||
this.jwks = createRemoteJWKSet(jwksUrl);
|
||||
this.jwksCreatedAt = now;
|
||||
|
||||
return this.jwks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a JWT token
|
||||
*/
|
||||
async verify(token: string): Promise<VerifyResult> {
|
||||
try {
|
||||
const jwks = this.getJWKS();
|
||||
|
||||
const result: JWTVerifyResult = await jwtVerify(token, jwks, {
|
||||
issuer: this.config.issuer,
|
||||
audience: this.config.audience,
|
||||
});
|
||||
|
||||
const payload = result.payload as unknown as JWTPayload;
|
||||
|
||||
// Check required scopes if configured
|
||||
if (this.config.requiredScopes.length > 0) {
|
||||
const tokenScopes = (payload.scope || '').split(' ').filter(Boolean);
|
||||
const hasRequiredScope = this.config.requiredScopes.some(s => tokenScopes.includes(s));
|
||||
|
||||
if (!hasRequiredScope) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Missing required scope. Required: ${this.config.requiredScopes.join(', ')}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
payload,
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Token verification failed';
|
||||
return {
|
||||
valid: false,
|
||||
error: message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify token and extract user info
|
||||
*/
|
||||
async authenticate(token: string): Promise<AuthenticatedUser | null> {
|
||||
const result = await this.verify(token);
|
||||
|
||||
if (!result.valid || !result.payload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const payload = result.payload;
|
||||
const scopes = (payload.scope || '').split(' ').filter(Boolean);
|
||||
|
||||
return {
|
||||
id: payload[this.config.userIdClaim] as string || payload.sub,
|
||||
userId: payload.userId,
|
||||
tenantId: payload.tenantId,
|
||||
email: payload.email,
|
||||
username: payload.username,
|
||||
roles: payload.roles || [],
|
||||
scopes,
|
||||
claims: payload,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has any of the specified scopes
|
||||
*/
|
||||
hasScope(user: AuthenticatedUser, scopes: string | string[]): boolean {
|
||||
const required = Array.isArray(scopes) ? scopes : [scopes];
|
||||
return required.some(scope => user.scopes.includes(scope));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has all of the specified scopes
|
||||
*/
|
||||
hasAllScopes(user: AuthenticatedUser, scopes: string[]): boolean {
|
||||
return scopes.every(scope => user.scopes.includes(scope));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has any of the specified roles
|
||||
*/
|
||||
hasRole(user: AuthenticatedUser, roles: string | string[]): boolean {
|
||||
const required = Array.isArray(roles) ? roles : [roles];
|
||||
return required.some(role => user.roles.includes(role));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has all of the specified roles
|
||||
*/
|
||||
hasAllRoles(user: AuthenticatedUser, roles: string[]): boolean {
|
||||
return roles.every(role => user.roles.includes(role));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear JWKS cache (force refresh on next verify)
|
||||
*/
|
||||
clearCache(): void {
|
||||
this.jwks = null;
|
||||
this.jwksCreatedAt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new IAM verifier instance
|
||||
*/
|
||||
export function createIAMVerifier(config: IAMServerConfig): IAMVerifier {
|
||||
return new IAMVerifier(config);
|
||||
}
|
||||
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2020"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
14
tsup.config.ts
Normal file
14
tsup.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig({
|
||||
entry: {
|
||||
index: 'src/index.ts',
|
||||
express: 'src/express/index.ts',
|
||||
},
|
||||
format: ['cjs', 'esm'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
external: ['express'],
|
||||
})
|
||||
Reference in New Issue
Block a user