UNPKG

@nodearch/keycloak

Version:
104 lines 4.37 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; import jwt from 'jsonwebtoken'; import jwksClient from 'jwks-rsa'; import { Service } from '@nodearch/core'; import { KeycloakConfig } from './keycloak.config.js'; let KeycloakAuth = class KeycloakAuth { constructor(keycloakConfig) { this.keycloakConfig = keycloakConfig; this.realmPattern = /^[a-zA-Z0-9_-]+$/; } async auth(token, realmName) { const decodedToken = this.decodeToken(token); const realm = realmName || this.getRealmFromJWT(decodedToken); this.validateRealmName(realm); const jwksUri = `${this.keycloakConfig.hostname}/realms/${realm}/protocol/openid-connect/certs`; await this.verifyToken(jwksUri, decodedToken, token); this.verifyClaims(decodedToken); return { token, realm, info: decodedToken.payload }; } decodeToken(token) { const decoded = jwt.decode(token, { complete: true, json: true }); if (!decoded || !decoded.header.kid || !decoded.payload.iss) { throw new Error('auth token is invalid'); } return decoded; } getRealmFromJWT(decodedToken) { const realmJwtPath = this.keycloakConfig.realmJWTPath; let realmName; if (typeof realmJwtPath === 'function') { realmName = realmJwtPath({ ...decodedToken }); } else { realmName = decodedToken.payload[realmJwtPath]; } if (realmName) return realmName; else throw new Error('failed to get realm from token'); } validateRealmName(realm) { const matches = realm.match(this.realmPattern); if (!matches) throw new Error('invalid realm name pattern'); } async verifyToken(jwksUri, decodedToken, token) { return new Promise((resolve, reject) => { const client = jwksClient({ jwksUri }); client.getSigningKey(decodedToken.header.kid, (err, key) => { if (err || !key) { return reject(new Error('cannot verify token ' + err?.message)); } jwt.verify(token, key.getPublicKey(), { ...this.keycloakConfig.jwtVerify, complete: true }, (error, decoded) => { if (error || !decoded) { return reject(new Error('cannot verify token ' + error?.message)); } else if (!decoded) { reject(new Error('invalid token')); } resolve(decoded); }); }); }); } verifyClaims(decodedToken) { if (this.keycloakConfig.claims) { for (const claimName in this.keycloakConfig.claims) { const claimValueExp = this.keycloakConfig.claims[claimName]; const tokenClaimValue = decodedToken.payload[claimName]; let result = false; if (typeof claimValueExp === 'function') { result = claimValueExp(tokenClaimValue); } else if (tokenClaimValue !== claimValueExp) { result = false; } else { result = true; } if (!result) { throw new Error('invalid token claims'); } } } } }; KeycloakAuth = __decorate([ Service({ export: true }), __metadata("design:paramtypes", [KeycloakConfig]) ], KeycloakAuth); export { KeycloakAuth }; //# sourceMappingURL=auth.service.js.map