@criipto/verify-express
Version:
Accept MitID, NemID, Swedish BankID, Norwegian BankID and more logins in your Node.js app using Passport or plain Express.js
232 lines • 10.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CriiptoVerifyRedirectPassportStrategy = exports.CriiptoVerifyExpressRedirect = exports.CriiptoVerifyJwtPassportStrategy = exports.CriiptoVerifyExpressJwt = void 0;
require("./fetch-polyfill");
const oidc_1 = require("@criipto/oidc");
const jose_1 = require("jose");
const utils_1 = require("./utils");
const debug = require('debug')('@criipto/verify-express');
const errorDebug = require('debug')('@criipto/verify-express:error');
const clockTolerance = 5 * 60;
class OAuth2Error extends Error {
constructor(error, error_description, state) {
super(error + (error_description ? ` (${error_description})` : ''));
this.name = "OAuth2Error";
this.error = error;
this.error_description = error_description;
this.state = state;
}
}
exports.default = OAuth2Error;
class CriiptoVerifyExpressJwt {
constructor(options) {
this.options = options;
this.jwks = (0, jose_1.createRemoteJWKSet)(new URL(`https://${options.domain}/.well-known/jwks`));
this.configurationManager = new oidc_1.OpenIDConfigurationManager(`https://${options.domain}`, options.clientID, utils_1.memoryStorage);
}
async process(req) {
const jwt = (0, utils_1.extractBearerToken)(req);
if (!jwt)
return null;
try {
const { payload } = await (0, jose_1.jwtVerify)(jwt, this.jwks, {
issuer: `https://${this.options.domain}`,
audience: this.options.clientID,
clockTolerance
});
return payload;
}
catch (err) {
errorDebug(`Error verifying JWT: ${err.toString()}`);
if (err instanceof jose_1.errors.JWTClaimValidationFailed || err instanceof jose_1.errors.JWSInvalid || err instanceof jose_1.errors.JWTInvalid) {
return null;
}
throw err;
}
}
middleware() {
return (req, res, next) => {
this.process(req).then((payload) => {
if (payload) {
req.claims = payload;
next();
return;
}
return res.sendStatus(401);
})
.catch(err => {
errorDebug(err);
next(err);
});
};
}
}
exports.CriiptoVerifyExpressJwt = CriiptoVerifyExpressJwt;
class CriiptoVerifyJwtPassportStrategy {
constructor(options, claimsToUser) {
this.options = options;
this.claimsToUser = claimsToUser;
this.helper = new CriiptoVerifyExpressJwt(options);
}
authenticate(req) {
this.helper.process(req)
.then(this.claimsToUser.bind(this))
.then(this.success)
.catch(err => {
debug(err);
this.fail(err);
});
}
}
exports.CriiptoVerifyJwtPassportStrategy = CriiptoVerifyJwtPassportStrategy;
class CriiptoVerifyExpressRedirect {
constructor(options) {
this.options = options;
this.jwks = (0, jose_1.createRemoteJWKSet)(new URL(`https://${options.domain}/.well-known/jwks`));
this.configurationManager = new oidc_1.OpenIDConfigurationManager(`https://${options.domain}`, options.clientID, utils_1.memoryStorage);
}
async logout(req, res) {
req.session.verifyClaims = null;
const protocol = req.protocol;
const strategyOptions = this.options;
const postLogoutRedirectUri = strategyOptions.postLogoutRedirectUri ?? '/';
const redirectUri = new URL(postLogoutRedirectUri.startsWith('http') ? postLogoutRedirectUri : `${protocol}://${req.get('host')}${postLogoutRedirectUri}`);
const configuration = await this.configurationManager.fetch();
const logoutUrl = (0, oidc_1.buildLogoutURL)(configuration, {
post_logout_redirect_uri: redirectUri.href
});
res.redirect(logoutUrl.href);
}
async handleCode(req, redirectUri) {
if (req.query.error) {
throw new OAuth2Error(req.query.error, req.query.error_description, req.query.state);
}
if (req.query.code) {
const code = req.query.code;
if (!redirectUri)
throw new Error('Bad session state');
const configuration = await this.configurationManager.fetch();
const codeResponse = await (0, oidc_1.codeExchange)(configuration, {
redirect_uri: redirectUri,
code,
client_secret: this.options.clientSecret
});
if ("error" in codeResponse) {
throw new OAuth2Error(codeResponse.error, codeResponse.error_description, codeResponse.state);
}
const { payload } = await (0, jose_1.jwtVerify)(codeResponse.id_token, this.jwks, {
issuer: `https://${this.options.domain}`,
audience: this.options.clientID,
clockTolerance
});
return payload;
}
return null;
}
async authorizeURL(req, returnTo) {
const protocol = req.protocol;
const redirectUri = new URL(this.options.redirectUri.startsWith('http') ? this.options.redirectUri : `${protocol}://${req.get('host')}${this.options.redirectUri}`);
if (returnTo) {
redirectUri.searchParams.set('returnTo', returnTo);
}
const configuration = await this.configurationManager.fetch();
const beforeAuthorize = this.options.beforeAuthorize ?? ((r, i) => i);
const authorizeUrl = (0, oidc_1.buildAuthorizeURL)(configuration, beforeAuthorize(req, {
scope: 'openid',
redirect_uri: redirectUri.href,
response_mode: 'query',
response_type: 'code'
}));
authorizeUrl.searchParams.set('criipto_sdk', utils_1.CRIIPTO_SDK);
return { authorizeUrl, redirectUri };
}
middleware(options) {
return (req, res, next) => {
const strategyOptions = this.options;
const force = options?.force || false;
if (!req.session)
throw new Error('express-session is required when using redirect');
Promise.resolve().then(async () => {
const claimsJson = req.session.verifyClaims;
if (claimsJson) {
const claims = JSON.parse(claimsJson);
req.claims = claims;
if (!force) {
return next();
}
}
const payload = await this.handleCode(req, req.session.verifyRedirectUri);
if (payload) {
req.claims = payload;
req.session.verifyClaims = JSON.stringify(payload);
req.session.touch();
if (options.successReturnToOrRedirect) {
const returnTo = req.query.returnTo ?? options.successReturnToOrRedirect;
return res.redirect(returnTo);
}
return next();
}
const { authorizeUrl, redirectUri } = await this.authorizeURL(req, req.url !== strategyOptions.redirectUri ? undefined : req.url);
req.session.verifyRedirectUri = redirectUri.href,
req.session.touch();
res.redirect(authorizeUrl.href);
})
.catch(err => {
errorDebug(err);
const failureRedirect = options.failureRedirect ?? '/';
if (err instanceof OAuth2Error) {
return res.redirect(`${failureRedirect}?error=${encodeURIComponent(err.error)}&error_description=${encodeURIComponent(err.error_description || '')}&state=${err.state || ''}`);
}
return res.redirect(`${failureRedirect}?error=${encodeURIComponent(err.toString())}`);
});
};
}
}
exports.CriiptoVerifyExpressRedirect = CriiptoVerifyExpressRedirect;
class CriiptoVerifyRedirectPassportStrategy {
constructor(options, claimsToUser) {
this.options = options;
this.claimsToUser = claimsToUser;
this.jwks = (0, jose_1.createRemoteJWKSet)(new URL(`https://${options.domain}/.well-known/jwks`));
this.configurationManager = new oidc_1.OpenIDConfigurationManager(`https://${options.domain}`, options.clientID, utils_1.memoryStorage);
this.helper = new CriiptoVerifyExpressRedirect(options);
this.helper.configurationManager = this.configurationManager;
}
logout(req, res) {
req.logout(async () => {
this.helper.logout(req, res);
});
}
authenticate(req, options) {
const strategyOptions = this.options;
const force = options?.force || false;
const isAuthenticated = req.isAuthenticated();
if (!force && isAuthenticated)
return this.pass();
Promise.resolve().then(async () => {
const protocol = req.protocol;
const redirectUri = new URL(strategyOptions.redirectUri.startsWith('http') ? strategyOptions.redirectUri : `${protocol}://${req.get('host')}${strategyOptions.redirectUri}`);
const payload = await this.helper.handleCode(req, redirectUri.href);
if (payload) {
const user = await this.claimsToUser(payload);
return this.success(user);
}
const { authorizeUrl } = await this.helper.authorizeURL(req, undefined);
this.redirect(authorizeUrl.href);
})
.catch(err => {
errorDebug(err);
if (options.failureRedirect) {
if (err instanceof OAuth2Error) {
return this.redirect(`${options.failureRedirect}?error=${encodeURIComponent(err.error)}&error_description=${encodeURIComponent(err.error_description || '')}&state=${err.state || ''}`);
}
return this.redirect(`${options.failureRedirect}?error=${encodeURIComponent(err.toString())}`);
}
else {
this.fail(err);
}
});
}
}
exports.CriiptoVerifyRedirectPassportStrategy = CriiptoVerifyRedirectPassportStrategy;
//# sourceMappingURL=index.js.map