2021-08-31 10:24:42 +00:00
|
|
|
const joi = require('joi');
|
|
|
|
const url = require('url');
|
|
|
|
const querystring = require('querystring');
|
|
|
|
const got = require('got');
|
|
|
|
const config = require('src/config/index.js');
|
|
|
|
const { jwt } = require('src/utils/pkgs.js');
|
|
|
|
|
|
|
|
const mod = {};
|
|
|
|
module.exports = mod;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
mod.getAuthURL = state => {
|
|
|
|
const input = joi
|
|
|
|
.object({
|
|
|
|
authorized_endpoint: joi.string().required(),
|
|
|
|
token_endpoint: joi.string().required(),
|
|
|
|
client_id: joi.string().required(),
|
|
|
|
client_secret: joi.string().required(),
|
|
|
|
state: joi.string().allow('', null).default(''),
|
|
|
|
})
|
|
|
|
.unknown()
|
|
|
|
.validate({ ...config.sso, state });
|
|
|
|
if (input.error) throw new Error(input.error.message);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @type {{value: { authorized_endpoint: string, token_endpoint: string, client_id: string, client_secret: string, state: string }}}
|
|
|
|
*/
|
|
|
|
const { value } = input;
|
|
|
|
|
|
|
|
const redirectUri = new url.URL('/oauth/redirect', config.server.url);
|
|
|
|
|
|
|
|
const qs = {
|
|
|
|
client_id: value.client_id,
|
|
|
|
scope: 'openid',
|
|
|
|
response_type: 'code',
|
|
|
|
redirect_uri: redirectUri.toString(),
|
|
|
|
};
|
|
|
|
if (value.state) qs.state = state;
|
|
|
|
|
|
|
|
return `${value.authorized_endpoint}?${querystring.stringify(qs)}`;
|
|
|
|
};
|
|
|
|
|
2021-09-01 07:20:53 +00:00
|
|
|
/**
|
|
|
|
* @return {string}
|
|
|
|
*/
|
2021-08-31 10:24:42 +00:00
|
|
|
mod.getLogoutURL = () => {
|
|
|
|
const input = joi
|
|
|
|
.object({
|
|
|
|
logout_endpoint: joi.string().required(),
|
|
|
|
})
|
|
|
|
.unknown()
|
|
|
|
.validate({ ...config.sso });
|
|
|
|
if (input.error) throw new Error(input.error.message);
|
|
|
|
const redirectUri = new url.URL('/oauth/redirect', config.server.url);
|
|
|
|
|
|
|
|
const qs = { state: 'logout', redirect_uri: redirectUri.toString() };
|
|
|
|
|
|
|
|
return `${input.value.logout_endpoint}?${querystring.stringify(qs)}`;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @typedef SSOAccount
|
2021-09-01 07:20:53 +00:00
|
|
|
* @property {string} access_token
|
|
|
|
* @property {string} refresh_token
|
|
|
|
* @property {string} user_id
|
2021-08-31 10:24:42 +00:00
|
|
|
* @property {string} username
|
|
|
|
* @property {string} display_name
|
|
|
|
* @property {string} email
|
|
|
|
*/
|
|
|
|
|
|
|
|
mod.getToken = async (code, state) => {
|
|
|
|
const input = joi
|
|
|
|
.object({
|
|
|
|
authorized_endpoint: joi.string().required(),
|
|
|
|
token_endpoint: joi.string().required(),
|
|
|
|
client_id: joi.string().required(),
|
|
|
|
client_secret: joi.string().required(),
|
|
|
|
state: joi.string().required(),
|
|
|
|
code: joi.string().required(),
|
|
|
|
})
|
|
|
|
.unknown()
|
|
|
|
.validate({ ...config.sso, state, code });
|
|
|
|
|
|
|
|
if (input.error) throw new Error(input.error.message);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @type {{value: { authorized_endpoint: string, token_endpoint: string, client_id: string, client_secret: string, state: string, code: string }}}
|
|
|
|
*/
|
|
|
|
const { value } = input;
|
|
|
|
|
|
|
|
const redirectUri = new url.URL('/oauth/redirect', config.server.url);
|
|
|
|
|
|
|
|
const qs = {
|
|
|
|
client_id: value.client_id,
|
|
|
|
client_secret: value.client_secret,
|
|
|
|
redirect_uri: redirectUri.toString(),
|
|
|
|
code: value.code,
|
|
|
|
client_session_state: value.state,
|
|
|
|
grant_type: 'authorization_code',
|
|
|
|
};
|
|
|
|
|
|
|
|
const resp = await got.default.post(value.token_endpoint, {
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
|
|
},
|
|
|
|
body: querystring.stringify(qs),
|
|
|
|
responseType: 'json',
|
|
|
|
});
|
|
|
|
|
|
|
|
const { body } = resp;
|
|
|
|
if (!body) throw new Error('resopnse body empty');
|
|
|
|
|
2021-09-01 07:20:53 +00:00
|
|
|
const { id_token: idToken, access_token: accessToken, refresh_token: refreshToken } = body;
|
2021-08-31 10:24:42 +00:00
|
|
|
if (!idToken) throw new Error('get id token fail');
|
|
|
|
|
|
|
|
const decoded = jwt.decode(idToken);
|
|
|
|
if (!decoded || typeof decoded !== 'object') throw new Error('jwt decode fail');
|
2021-09-01 07:20:53 +00:00
|
|
|
console.log('decoded ::: ', decoded)
|
|
|
|
console.log('body ::: ', body)
|
2021-08-31 10:24:42 +00:00
|
|
|
// @ts-ignore
|
|
|
|
const { preferred_username: preferredUsername } = decoded;
|
|
|
|
if (!preferredUsername) throw new Error('id token field missing');
|
|
|
|
|
|
|
|
const displayName = `${decoded.family_name ?? ''}${decoded.given_name ?? ''}`;
|
|
|
|
|
|
|
|
/** @type {SSOAccount} */
|
|
|
|
const ssoAccount = {
|
2021-09-01 07:20:53 +00:00
|
|
|
access_token: accessToken,
|
|
|
|
refresh_token: refreshToken,
|
|
|
|
user_id: decoded.sub,
|
2021-08-31 10:24:42 +00:00
|
|
|
username: preferredUsername.toLowerCase(),
|
|
|
|
display_name: displayName ?? preferredUsername,
|
|
|
|
email: decoded.email ?? '',
|
|
|
|
};
|
|
|
|
|
|
|
|
return ssoAccount;
|
|
|
|
};
|