157 lines
4.2 KiB
JavaScript
157 lines
4.2 KiB
JavaScript
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: "offline_access",
|
|
response_type: "code",
|
|
redirect_uri: redirectUri.toString(),
|
|
};
|
|
if (value.state) qs.state = state;
|
|
|
|
return `${value.authorized_endpoint}?${querystring.stringify(qs)}`;
|
|
};
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
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)}`;
|
|
};
|
|
|
|
mod.getUserInfo = async (token) => {
|
|
const input = joi
|
|
.object()
|
|
.unknown()
|
|
.validateAsync({ ...config.sso, token });
|
|
};
|
|
|
|
/**
|
|
* @typedef SSOAccount
|
|
* @property {string} access_token
|
|
* @property {string} refresh_token
|
|
* @property {string} user_id
|
|
* @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");
|
|
|
|
const {
|
|
id_token: idToken,
|
|
access_token: accessToken,
|
|
refresh_token: refreshToken,
|
|
} = body;
|
|
// 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");
|
|
// console.log("decoded ::: ", decoded);
|
|
|
|
const decoded = jwt.decode(accessToken);
|
|
// decode access token
|
|
console.log("token ::: ", jwt.decode(accessToken));
|
|
|
|
console.log("body ::: ", body);
|
|
// @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 = {
|
|
access_token: accessToken,
|
|
refresh_token: refreshToken,
|
|
user_id: decoded.sub,
|
|
username: preferredUsername.toLowerCase(),
|
|
display_name: displayName ?? preferredUsername,
|
|
email: decoded.email ?? "",
|
|
};
|
|
|
|
return ssoAccount;
|
|
};
|