piral-oidc
Version:
Plugin to integrate authentication using OpenID connect in Piral.
201 lines • 8.31 kB
JavaScript
import { Log, UserManager, Logger } from 'oidc-client-ts';
import { OidcError } from './OidcError';
import { LogLevel, OidcErrorType } from './types';
const logLevelToOidcMap = {
[LogLevel.none]: 0,
[LogLevel.error]: 1,
[LogLevel.warn]: 2,
[LogLevel.info]: 3,
[LogLevel.debug]: 4,
};
function doesWindowLocationMatch(targetUri) {
return window.location.pathname === new URL(targetUri).pathname;
}
function convertLogLevelToOidcClient(level) {
return logLevelToOidcMap[level];
}
/**
* Sets up a new client wrapping the oidc-client API.
* @param config The configuration for the client.
*/
export function setupOidcClient(config) {
const { clientId, clientSecret, identityProviderUri, redirectUri = `${location.origin}/auth`, signInRedirectParams, postLogoutRedirectUri = location.origin, responseType, responseMode, scopes, restrict = false, parentName, appUri, logLevel, userStore, extraQueryParams, uiLocales, metadata, metadataUrl, monitorSession, } = config;
const isMainWindow = () => (parentName ? parentName === window.parent?.name : window === window.top);
const userManager = new UserManager({
authority: identityProviderUri,
redirect_uri: redirectUri,
silent_redirect_uri: redirectUri,
popup_redirect_uri: redirectUri,
post_logout_redirect_uri: postLogoutRedirectUri,
client_id: clientId,
client_secret: clientSecret,
response_type: responseType,
scope: scopes?.join(' '),
userStore,
extraQueryParams,
ui_locales: uiLocales,
response_mode: responseMode,
metadata,
metadataUrl,
monitorSession,
});
if (logLevel !== undefined) {
Log.setLogger(console);
Log.setLevel(convertLogLevelToOidcClient(logLevel));
}
else if (process.env.NODE_ENV === 'development') {
Log.setLogger(console);
Log.setLevel(Log.DEBUG);
}
if (doesWindowLocationMatch(userManager.settings.post_logout_redirect_uri)) {
if (isMainWindow()) {
userManager.signoutRedirectCallback();
}
else {
userManager.signoutPopupCallback();
}
}
const retrieveToken = () => {
return new Promise((res, rej) => {
userManager
.getUser()
.then((user) => {
if (!user) {
rej(new OidcError(OidcErrorType.notAuthorized));
}
else if (user.access_token && user.expires_in > 60) {
res(user.access_token);
}
else {
return userManager.signinSilent().then((user) => {
if (!user) {
return rej(new OidcError(OidcErrorType.silentRenewFailed));
}
if (!user.access_token) {
return rej(new OidcError(OidcErrorType.invalidToken));
}
return res(user.access_token);
});
}
})
.catch((err) => rej(new OidcError(OidcErrorType.unknown, err)));
});
};
const retrieveProfile = () => {
return new Promise((res, rej) => {
userManager.getUser().then((user) => {
if (!user || user.expires_in <= 0) {
return rej(new OidcError(OidcErrorType.notAuthorized));
}
else {
return res(user.profile);
}
}, (err) => rej(new OidcError(OidcErrorType.unknown, err)));
});
};
const handleAuthentication = () => new Promise(async (resolve, reject) => {
/** The user that is resolved when finishing the callback */
let user;
if ((doesWindowLocationMatch(userManager.settings.silent_redirect_uri) ||
doesWindowLocationMatch(userManager.settings.popup_redirect_uri)) &&
!isMainWindow()) {
/*
* This is a silent redirect frame. The correct behavior is to notify the parent of the updated user,
* and then to do nothing else. Encountering an error here means the background IFrame failed
* to update the parent. This is usually due to a timeout from a network error.
*/
try {
await userManager.signinSilentCallback();
user = await userManager.getUser();
}
catch (e) {
return reject(new OidcError(OidcErrorType.oidcCallback, e));
}
return resolve({
shouldRender: false,
state: user?.state,
});
}
if (doesWindowLocationMatch(userManager.settings.redirect_uri) && isMainWindow()) {
try {
user = await userManager.signinCallback();
}
catch (e) {
/*
* Failing to handle a sign-in callback is non-recoverable. The user is expected to call `logout()`, after
* logging this error to their internal error-handling service. Usually, this is due to a misconfigured auth server.
*/
return reject(new OidcError(OidcErrorType.oidcCallback, e));
}
if (appUri) {
Logger.debug(`Redirecting to ${appUri} due to appUri being configured.`);
window.location.href = appUri;
return resolve({
shouldRender: false,
state: user?.state,
});
}
/* If appUri is not configured, we let the user decide what to do after getting a session. */
return resolve({
shouldRender: true,
state: user?.state,
});
}
/*
* The current page is a normal flow, not a callback or signout. We should retrieve the current access_token,
* or log the user in if there is no current session.
* This branch of code should also tell the user to render the main application.
*/
return retrieveToken()
.then((token) => {
if (token) {
return resolve({ shouldRender: true });
}
else {
/* We should never get into this state, retrieveToken() should reject if there is no token */
return reject(new OidcError(OidcErrorType.invalidToken));
}
})
.catch(async (reason) => {
if (reason.type === OidcErrorType.notAuthorized) {
/*
* Expected Error during normal code flow:
* This is the first time logging in since a logout (or ever), instead of asking the user
* to call `login()`, just perform it ourself here.
*
* The resolve shouldn't matter, as `signinRedirect` will redirect the browser location
* to the user's configured redirectUri.
*/
await userManager.signinRedirect(signInRedirectParams);
return resolve({ shouldRender: false });
}
/*
* Getting here is a non-recoverable error. It is up to the user to determine what to do.
* Usually this is a result of failing to reach the authentication server, or a misconfigured
* authentication server, or a bad clock skew (commonly caused by docker in windows).
*/
return reject(reason);
});
});
return {
_: userManager,
login() {
return userManager.signinRedirect(signInRedirectParams);
},
logout() {
return userManager.signoutRedirect();
},
revoke() {
return userManager.revokeTokens();
},
handleAuthentication,
extendHeaders(req) {
if (!restrict) {
req.setHeaders(retrieveToken().then((token) => token && { Authorization: `Bearer ${token}` }, () => undefined));
}
},
token: retrieveToken,
account: retrieveProfile,
};
}
//# sourceMappingURL=setup.js.map