Fixed build warnings
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:
62
dist/express.d.mts
vendored
Normal file
62
dist/express.d.mts
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { AuthenticatedUser, IAMServerConfig, AuthOptions } from './index.mjs';
|
||||
export { IAMVerifier, createIAMVerifier } from './index.mjs';
|
||||
|
||||
/**
|
||||
* Express middleware for IAM authentication
|
||||
*/
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: AuthenticatedUser;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
declare function createAuthMiddleware(config: IAMServerConfig): (options?: AuthOptions) => (req: Request, res: Response, next: NextFunction) => Promise<Response<any, Record<string, any>> | undefined>;
|
||||
/**
|
||||
* 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);
|
||||
* ```
|
||||
*/
|
||||
declare function requireRole(roles: string | string[]): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
|
||||
/**
|
||||
* Create scope-checking middleware (use after auth middleware)
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* app.get('/data', auth(), requireScope('read:data'), handler);
|
||||
* ```
|
||||
*/
|
||||
declare function requireScope(scopes: string | string[]): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
|
||||
/**
|
||||
* Optional auth - attaches user if token present, but doesn't require it
|
||||
*/
|
||||
declare function createOptionalAuthMiddleware(config: IAMServerConfig): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
||||
|
||||
export { AuthOptions, AuthenticatedUser, IAMServerConfig, createAuthMiddleware, createOptionalAuthMiddleware, requireRole, requireScope };
|
||||
62
dist/express.d.ts
vendored
Normal file
62
dist/express.d.ts
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { AuthenticatedUser, IAMServerConfig, AuthOptions } from './index.js';
|
||||
export { IAMVerifier, createIAMVerifier } from './index.js';
|
||||
|
||||
/**
|
||||
* Express middleware for IAM authentication
|
||||
*/
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: AuthenticatedUser;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
declare function createAuthMiddleware(config: IAMServerConfig): (options?: AuthOptions) => (req: Request, res: Response, next: NextFunction) => Promise<Response<any, Record<string, any>> | undefined>;
|
||||
/**
|
||||
* 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);
|
||||
* ```
|
||||
*/
|
||||
declare function requireRole(roles: string | string[]): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
|
||||
/**
|
||||
* Create scope-checking middleware (use after auth middleware)
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* app.get('/data', auth(), requireScope('read:data'), handler);
|
||||
* ```
|
||||
*/
|
||||
declare function requireScope(scopes: string | string[]): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
|
||||
/**
|
||||
* Optional auth - attaches user if token present, but doesn't require it
|
||||
*/
|
||||
declare function createOptionalAuthMiddleware(config: IAMServerConfig): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
||||
|
||||
export { AuthOptions, AuthenticatedUser, IAMServerConfig, createAuthMiddleware, createOptionalAuthMiddleware, requireRole, requireScope };
|
||||
264
dist/express.js
vendored
Normal file
264
dist/express.js
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/express/index.ts
|
||||
var express_exports = {};
|
||||
__export(express_exports, {
|
||||
IAMVerifier: () => IAMVerifier,
|
||||
createAuthMiddleware: () => createAuthMiddleware,
|
||||
createIAMVerifier: () => createIAMVerifier,
|
||||
createOptionalAuthMiddleware: () => createOptionalAuthMiddleware,
|
||||
requireRole: () => requireRole,
|
||||
requireScope: () => requireScope
|
||||
});
|
||||
module.exports = __toCommonJS(express_exports);
|
||||
|
||||
// src/verifier.ts
|
||||
var import_jose = require("jose");
|
||||
var IAMVerifier = class {
|
||||
constructor(config) {
|
||||
this.jwks = null;
|
||||
this.jwksCreatedAt = 0;
|
||||
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
|
||||
*/
|
||||
getJWKS() {
|
||||
const now = Date.now();
|
||||
const ttlMs = this.config.cacheTTL * 1e3;
|
||||
if (this.jwks && this.config.cacheKeys && now - this.jwksCreatedAt < ttlMs) {
|
||||
return this.jwks;
|
||||
}
|
||||
const jwksUrl = new URL(`${this.config.issuer}/.well-known/jwks.json`);
|
||||
this.jwks = (0, import_jose.createRemoteJWKSet)(jwksUrl);
|
||||
this.jwksCreatedAt = now;
|
||||
return this.jwks;
|
||||
}
|
||||
/**
|
||||
* Verify a JWT token
|
||||
*/
|
||||
async verify(token) {
|
||||
try {
|
||||
const jwks = this.getJWKS();
|
||||
const result = await (0, import_jose.jwtVerify)(token, jwks, {
|
||||
issuer: this.config.issuer,
|
||||
audience: this.config.audience
|
||||
});
|
||||
const payload = result.payload;
|
||||
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) {
|
||||
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] || 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, scopes) {
|
||||
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, scopes) {
|
||||
return scopes.every((scope) => user.scopes.includes(scope));
|
||||
}
|
||||
/**
|
||||
* Check if user has any of the specified roles
|
||||
*/
|
||||
hasRole(user, roles) {
|
||||
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, roles) {
|
||||
return roles.every((role) => user.roles.includes(role));
|
||||
}
|
||||
/**
|
||||
* Clear JWKS cache (force refresh on next verify)
|
||||
*/
|
||||
clearCache() {
|
||||
this.jwks = null;
|
||||
this.jwksCreatedAt = 0;
|
||||
}
|
||||
};
|
||||
function createIAMVerifier(config) {
|
||||
return new IAMVerifier(config);
|
||||
}
|
||||
|
||||
// src/express/index.ts
|
||||
function extractToken(req) {
|
||||
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];
|
||||
}
|
||||
function createAuthMiddleware(config) {
|
||||
const verifier = createIAMVerifier(config);
|
||||
return function auth(options = {}) {
|
||||
return async (req, res, next) => {
|
||||
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"
|
||||
});
|
||||
}
|
||||
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(", ")}`
|
||||
});
|
||||
}
|
||||
}
|
||||
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 ")}`
|
||||
});
|
||||
}
|
||||
}
|
||||
req.user = user;
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
function requireRole(roles) {
|
||||
const required = Array.isArray(roles) ? roles : [roles];
|
||||
return (req, res, next) => {
|
||||
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();
|
||||
};
|
||||
}
|
||||
function requireScope(scopes) {
|
||||
const required = Array.isArray(scopes) ? scopes : [scopes];
|
||||
return (req, res, next) => {
|
||||
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();
|
||||
};
|
||||
}
|
||||
function createOptionalAuthMiddleware(config) {
|
||||
const verifier = createIAMVerifier(config);
|
||||
return async (req, res, next) => {
|
||||
const token = extractToken(req);
|
||||
if (token) {
|
||||
const user = await verifier.authenticate(token);
|
||||
if (user) {
|
||||
req.user = user;
|
||||
}
|
||||
}
|
||||
next();
|
||||
};
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
IAMVerifier,
|
||||
createAuthMiddleware,
|
||||
createIAMVerifier,
|
||||
createOptionalAuthMiddleware,
|
||||
requireRole,
|
||||
requireScope
|
||||
});
|
||||
//# sourceMappingURL=express.js.map
|
||||
1
dist/express.js.map
vendored
Normal file
1
dist/express.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
232
dist/express.mjs
vendored
Normal file
232
dist/express.mjs
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
// src/verifier.ts
|
||||
import { createRemoteJWKSet, jwtVerify } from "jose";
|
||||
var IAMVerifier = class {
|
||||
constructor(config) {
|
||||
this.jwks = null;
|
||||
this.jwksCreatedAt = 0;
|
||||
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
|
||||
*/
|
||||
getJWKS() {
|
||||
const now = Date.now();
|
||||
const ttlMs = this.config.cacheTTL * 1e3;
|
||||
if (this.jwks && this.config.cacheKeys && now - this.jwksCreatedAt < ttlMs) {
|
||||
return this.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) {
|
||||
try {
|
||||
const jwks = this.getJWKS();
|
||||
const result = await jwtVerify(token, jwks, {
|
||||
issuer: this.config.issuer,
|
||||
audience: this.config.audience
|
||||
});
|
||||
const payload = result.payload;
|
||||
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) {
|
||||
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] || 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, scopes) {
|
||||
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, scopes) {
|
||||
return scopes.every((scope) => user.scopes.includes(scope));
|
||||
}
|
||||
/**
|
||||
* Check if user has any of the specified roles
|
||||
*/
|
||||
hasRole(user, roles) {
|
||||
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, roles) {
|
||||
return roles.every((role) => user.roles.includes(role));
|
||||
}
|
||||
/**
|
||||
* Clear JWKS cache (force refresh on next verify)
|
||||
*/
|
||||
clearCache() {
|
||||
this.jwks = null;
|
||||
this.jwksCreatedAt = 0;
|
||||
}
|
||||
};
|
||||
function createIAMVerifier(config) {
|
||||
return new IAMVerifier(config);
|
||||
}
|
||||
|
||||
// src/express/index.ts
|
||||
function extractToken(req) {
|
||||
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];
|
||||
}
|
||||
function createAuthMiddleware(config) {
|
||||
const verifier = createIAMVerifier(config);
|
||||
return function auth(options = {}) {
|
||||
return async (req, res, next) => {
|
||||
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"
|
||||
});
|
||||
}
|
||||
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(", ")}`
|
||||
});
|
||||
}
|
||||
}
|
||||
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 ")}`
|
||||
});
|
||||
}
|
||||
}
|
||||
req.user = user;
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
function requireRole(roles) {
|
||||
const required = Array.isArray(roles) ? roles : [roles];
|
||||
return (req, res, next) => {
|
||||
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();
|
||||
};
|
||||
}
|
||||
function requireScope(scopes) {
|
||||
const required = Array.isArray(scopes) ? scopes : [scopes];
|
||||
return (req, res, next) => {
|
||||
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();
|
||||
};
|
||||
}
|
||||
function createOptionalAuthMiddleware(config) {
|
||||
const verifier = createIAMVerifier(config);
|
||||
return async (req, res, next) => {
|
||||
const token = extractToken(req);
|
||||
if (token) {
|
||||
const user = await verifier.authenticate(token);
|
||||
if (user) {
|
||||
req.user = user;
|
||||
}
|
||||
}
|
||||
next();
|
||||
};
|
||||
}
|
||||
export {
|
||||
IAMVerifier,
|
||||
createAuthMiddleware,
|
||||
createIAMVerifier,
|
||||
createOptionalAuthMiddleware,
|
||||
requireRole,
|
||||
requireScope
|
||||
};
|
||||
//# sourceMappingURL=express.mjs.map
|
||||
1
dist/express.mjs.map
vendored
Normal file
1
dist/express.mjs.map
vendored
Normal file
File diff suppressed because one or more lines are too long
128
dist/index.d.mts
vendored
Normal file
128
dist/index.d.mts
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* @armco/iam-server Types
|
||||
*/
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
interface VerifyResult {
|
||||
valid: boolean;
|
||||
payload?: JWTPayload;
|
||||
error?: string;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT Verifier using jose library
|
||||
* Fetches JWKS from IAM and validates tokens
|
||||
*/
|
||||
|
||||
declare class IAMVerifier {
|
||||
private config;
|
||||
private jwks;
|
||||
private jwksCreatedAt;
|
||||
constructor(config: IAMServerConfig);
|
||||
/**
|
||||
* Get or create JWKS
|
||||
*/
|
||||
private getJWKS;
|
||||
/**
|
||||
* Verify a JWT token
|
||||
*/
|
||||
verify(token: string): Promise<VerifyResult>;
|
||||
/**
|
||||
* Verify token and extract user info
|
||||
*/
|
||||
authenticate(token: string): Promise<AuthenticatedUser | null>;
|
||||
/**
|
||||
* Check if user has any of the specified scopes
|
||||
*/
|
||||
hasScope(user: AuthenticatedUser, scopes: string | string[]): boolean;
|
||||
/**
|
||||
* Check if user has all of the specified scopes
|
||||
*/
|
||||
hasAllScopes(user: AuthenticatedUser, scopes: string[]): boolean;
|
||||
/**
|
||||
* Check if user has any of the specified roles
|
||||
*/
|
||||
hasRole(user: AuthenticatedUser, roles: string | string[]): boolean;
|
||||
/**
|
||||
* Check if user has all of the specified roles
|
||||
*/
|
||||
hasAllRoles(user: AuthenticatedUser, roles: string[]): boolean;
|
||||
/**
|
||||
* Clear JWKS cache (force refresh on next verify)
|
||||
*/
|
||||
clearCache(): void;
|
||||
}
|
||||
/**
|
||||
* Create a new IAM verifier instance
|
||||
*/
|
||||
declare function createIAMVerifier(config: IAMServerConfig): IAMVerifier;
|
||||
|
||||
export { type AuthOptions, type AuthenticatedUser, type IAMServerConfig, IAMVerifier, type JWTPayload, type VerifyResult, createIAMVerifier };
|
||||
128
dist/index.d.ts
vendored
Normal file
128
dist/index.d.ts
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* @armco/iam-server Types
|
||||
*/
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
interface VerifyResult {
|
||||
valid: boolean;
|
||||
payload?: JWTPayload;
|
||||
error?: string;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT Verifier using jose library
|
||||
* Fetches JWKS from IAM and validates tokens
|
||||
*/
|
||||
|
||||
declare class IAMVerifier {
|
||||
private config;
|
||||
private jwks;
|
||||
private jwksCreatedAt;
|
||||
constructor(config: IAMServerConfig);
|
||||
/**
|
||||
* Get or create JWKS
|
||||
*/
|
||||
private getJWKS;
|
||||
/**
|
||||
* Verify a JWT token
|
||||
*/
|
||||
verify(token: string): Promise<VerifyResult>;
|
||||
/**
|
||||
* Verify token and extract user info
|
||||
*/
|
||||
authenticate(token: string): Promise<AuthenticatedUser | null>;
|
||||
/**
|
||||
* Check if user has any of the specified scopes
|
||||
*/
|
||||
hasScope(user: AuthenticatedUser, scopes: string | string[]): boolean;
|
||||
/**
|
||||
* Check if user has all of the specified scopes
|
||||
*/
|
||||
hasAllScopes(user: AuthenticatedUser, scopes: string[]): boolean;
|
||||
/**
|
||||
* Check if user has any of the specified roles
|
||||
*/
|
||||
hasRole(user: AuthenticatedUser, roles: string | string[]): boolean;
|
||||
/**
|
||||
* Check if user has all of the specified roles
|
||||
*/
|
||||
hasAllRoles(user: AuthenticatedUser, roles: string[]): boolean;
|
||||
/**
|
||||
* Clear JWKS cache (force refresh on next verify)
|
||||
*/
|
||||
clearCache(): void;
|
||||
}
|
||||
/**
|
||||
* Create a new IAM verifier instance
|
||||
*/
|
||||
declare function createIAMVerifier(config: IAMServerConfig): IAMVerifier;
|
||||
|
||||
export { type AuthOptions, type AuthenticatedUser, type IAMServerConfig, IAMVerifier, type JWTPayload, type VerifyResult, createIAMVerifier };
|
||||
153
dist/index.js
vendored
Normal file
153
dist/index.js
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/index.ts
|
||||
var src_exports = {};
|
||||
__export(src_exports, {
|
||||
IAMVerifier: () => IAMVerifier,
|
||||
createIAMVerifier: () => createIAMVerifier
|
||||
});
|
||||
module.exports = __toCommonJS(src_exports);
|
||||
|
||||
// src/verifier.ts
|
||||
var import_jose = require("jose");
|
||||
var IAMVerifier = class {
|
||||
constructor(config) {
|
||||
this.jwks = null;
|
||||
this.jwksCreatedAt = 0;
|
||||
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
|
||||
*/
|
||||
getJWKS() {
|
||||
const now = Date.now();
|
||||
const ttlMs = this.config.cacheTTL * 1e3;
|
||||
if (this.jwks && this.config.cacheKeys && now - this.jwksCreatedAt < ttlMs) {
|
||||
return this.jwks;
|
||||
}
|
||||
const jwksUrl = new URL(`${this.config.issuer}/.well-known/jwks.json`);
|
||||
this.jwks = (0, import_jose.createRemoteJWKSet)(jwksUrl);
|
||||
this.jwksCreatedAt = now;
|
||||
return this.jwks;
|
||||
}
|
||||
/**
|
||||
* Verify a JWT token
|
||||
*/
|
||||
async verify(token) {
|
||||
try {
|
||||
const jwks = this.getJWKS();
|
||||
const result = await (0, import_jose.jwtVerify)(token, jwks, {
|
||||
issuer: this.config.issuer,
|
||||
audience: this.config.audience
|
||||
});
|
||||
const payload = result.payload;
|
||||
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) {
|
||||
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] || 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, scopes) {
|
||||
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, scopes) {
|
||||
return scopes.every((scope) => user.scopes.includes(scope));
|
||||
}
|
||||
/**
|
||||
* Check if user has any of the specified roles
|
||||
*/
|
||||
hasRole(user, roles) {
|
||||
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, roles) {
|
||||
return roles.every((role) => user.roles.includes(role));
|
||||
}
|
||||
/**
|
||||
* Clear JWKS cache (force refresh on next verify)
|
||||
*/
|
||||
clearCache() {
|
||||
this.jwks = null;
|
||||
this.jwksCreatedAt = 0;
|
||||
}
|
||||
};
|
||||
function createIAMVerifier(config) {
|
||||
return new IAMVerifier(config);
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
IAMVerifier,
|
||||
createIAMVerifier
|
||||
});
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
dist/index.js.map
vendored
Normal file
1
dist/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
125
dist/index.mjs
vendored
Normal file
125
dist/index.mjs
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
// src/verifier.ts
|
||||
import { createRemoteJWKSet, jwtVerify } from "jose";
|
||||
var IAMVerifier = class {
|
||||
constructor(config) {
|
||||
this.jwks = null;
|
||||
this.jwksCreatedAt = 0;
|
||||
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
|
||||
*/
|
||||
getJWKS() {
|
||||
const now = Date.now();
|
||||
const ttlMs = this.config.cacheTTL * 1e3;
|
||||
if (this.jwks && this.config.cacheKeys && now - this.jwksCreatedAt < ttlMs) {
|
||||
return this.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) {
|
||||
try {
|
||||
const jwks = this.getJWKS();
|
||||
const result = await jwtVerify(token, jwks, {
|
||||
issuer: this.config.issuer,
|
||||
audience: this.config.audience
|
||||
});
|
||||
const payload = result.payload;
|
||||
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) {
|
||||
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] || 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, scopes) {
|
||||
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, scopes) {
|
||||
return scopes.every((scope) => user.scopes.includes(scope));
|
||||
}
|
||||
/**
|
||||
* Check if user has any of the specified roles
|
||||
*/
|
||||
hasRole(user, roles) {
|
||||
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, roles) {
|
||||
return roles.every((role) => user.roles.includes(role));
|
||||
}
|
||||
/**
|
||||
* Clear JWKS cache (force refresh on next verify)
|
||||
*/
|
||||
clearCache() {
|
||||
this.jwks = null;
|
||||
this.jwksCreatedAt = 0;
|
||||
}
|
||||
};
|
||||
function createIAMVerifier(config) {
|
||||
return new IAMVerifier(config);
|
||||
}
|
||||
export {
|
||||
IAMVerifier,
|
||||
createIAMVerifier
|
||||
};
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
1
dist/index.mjs.map
vendored
Normal file
1
dist/index.mjs.map
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -7,14 +7,14 @@
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"./express": {
|
||||
"types": "./dist/express.d.ts",
|
||||
"import": "./dist/express.mjs",
|
||||
"require": "./dist/express.js",
|
||||
"types": "./dist/express.d.ts"
|
||||
"require": "./dist/express.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
|
||||
Reference in New Issue
Block a user