@nodearch/keycloak
Version:
NodeArch Keycloak Wrapper
104 lines • 4.37 kB
JavaScript
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