strapi-plugin-sso
Version:
Plug-in for single sign-on with Strapi!
150 lines (130 loc) • 5.41 kB
JavaScript
;
const axios = require("axios");
const {v4} = require('uuid');
const pkceChallenge = require("pkce-challenge").default;
const {Buffer} = require('buffer');
const configValidation = () => {
const config = strapi.config.get('plugin.strapi-plugin-sso')
if (config['COGNITO_OAUTH_CLIENT_ID'] && config['COGNITO_OAUTH_CLIENT_SECRET'] && config['COGNITO_OAUTH_DOMAIN']) {
return config
}
throw new Error('COGNITO_OAUTH_CLIENT_ID, COGNITO_OAUTH_CLIENT_SECRET AND COGNITO_OAUTH_DOMAIN are required')
}
/**
* Common constants
*/
const OAUTH_ENDPOINT = (domain, region) => {
return `https://${domain}.auth.${region}.amazoncognito.com/oauth2/authorize`
}
const OAUTH_TOKEN_ENDPOINT = (domain, region) => {
return `https://${domain}.auth.${region}.amazoncognito.com/oauth2/token`
}
const OAUTH_USER_INFO_ENDPOINT = (domain, region) => {
return `https://${domain}.auth.${region}.amazoncognito.com/oauth2/userInfo`
}
const OAUTH_GRANT_TYPE = 'authorization_code'
const OAUTH_SCOPE = 'openid email profile'
const OAUTH_RESPONSE_TYPE = 'code'
async function cognitoSignIn(ctx) {
const config = configValidation()
const endpoint = OAUTH_ENDPOINT(config['COGNITO_OAUTH_DOMAIN'], config['COGNITO_OAUTH_REGION'])
// Generate code verifier and code challenge
const { code_verifier: codeVerifier, code_challenge: codeChallenge } =
pkceChallenge();
// Store the code verifier in the session
ctx.session.codeVerifier = codeVerifier;
const state = crypto.getRandomValues(Buffer.alloc(32)).toString('base64url');
ctx.session.oidcState = state;
const params = new URLSearchParams();
params.append('client_id', config['COGNITO_OAUTH_CLIENT_ID']);
params.append('redirect_uri', config['COGNITO_OAUTH_REDIRECT_URI']);
params.append('scope', OAUTH_SCOPE);
params.append('response_type', OAUTH_RESPONSE_TYPE);
params.append('code_challenge', codeChallenge);
params.append('code_challenge_method', 'S256');
params.append('state', state);
const url = `${endpoint}?${params.toString()}`
ctx.set('Location', url)
return ctx.send({}, 302)
}
async function cognitoSignInCallback(ctx) {
const config = configValidation()
const userService = strapi.service('admin::user')
const tokenService = strapi.service('admin::token')
const oauthService = strapi.plugin('strapi-plugin-sso').service('oauth')
const roleService = strapi.plugin('strapi-plugin-sso').service('role')
if (!ctx.query.code) {
return ctx.send(oauthService.renderSignUpError(`code Not Found`))
}
if (!ctx.query.state || ctx.query.state !== ctx.session.oidcState) {
return ctx.send(oauthService.renderSignUpError(`Invalid state`))
}
const params = new URLSearchParams();
params.append('code', ctx.query.code);
params.append('client_id', config['COGNITO_OAUTH_CLIENT_ID']);
params.append('client_secret', config['COGNITO_OAUTH_CLIENT_SECRET']);
params.append('redirect_uri', config['COGNITO_OAUTH_REDIRECT_URI']);
params.append('grant_type', OAUTH_GRANT_TYPE);
// Include the code verifier from the session
params.append("code_verifier", ctx.session.codeVerifier);
try {
const tokenEndpoint = OAUTH_TOKEN_ENDPOINT(config['COGNITO_OAUTH_DOMAIN'], config['COGNITO_OAUTH_REGION'])
const userInfoEndpoint = OAUTH_USER_INFO_ENDPOINT(config['COGNITO_OAUTH_DOMAIN'], config['COGNITO_OAUTH_REGION'])
const response = await axios.post(tokenEndpoint, params, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
const userResponse = await axios.get(userInfoEndpoint, {
headers: {
Authorization: `Bearer ${response.data.access_token}`
}
})
if (userResponse.data.email_verified !== 'true') {
throw new Error('Your email address has not been verified.')
}
const userGroup = config['COGNITO_USER_GROUP'];
if (userGroup) {
const claims = JSON.parse(Buffer.from(response.data.access_token.split('.')[1], 'base64').toString());
if ((claims['cognito:groups'] || []).includes(userGroup) === false) {
throw new Error('You do not belong to the specified user group.');
}
}
const dbUser = await userService.findOneByEmail(userResponse.data.email)
let activateUser;
let jwtToken;
if (dbUser) {
activateUser = dbUser;
jwtToken = await tokenService.createJwtToken(dbUser)
} else {
const cognitoRoles = await roleService.cognitoRoles()
const roles = cognitoRoles && cognitoRoles['roles'] ? cognitoRoles['roles'].map(role => ({
id: role
})) : []
const defaultLocale = oauthService.localeFindByHeader(ctx.request.headers)
activateUser = await oauthService.createUser(
userResponse.data.email,
'',
userResponse.data.username,
defaultLocale,
roles
)
jwtToken = await tokenService.createJwtToken(activateUser)
// Trigger webhook
await oauthService.triggerWebHook(activateUser)
}
// Login Event Call
oauthService.triggerSignInSuccess(activateUser)
const nonce = v4()
const html = oauthService.renderSignUpSuccess(jwtToken, activateUser, nonce)
ctx.set('Content-Security-Policy', `script-src 'nonce-${nonce}'`)
ctx.send(html);
} catch (e) {
console.error(e)
ctx.send(oauthService.renderSignUpError(e.message))
}
}
module.exports = {
cognitoSignIn,
cognitoSignInCallback
}