This commit is contained in:
Jay
2021-09-01 20:46:41 +08:00
parent b91ce62aa4
commit 2e05f90851
19 changed files with 3061 additions and 11174 deletions
+136 -72
View File
@@ -1,12 +1,11 @@
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 joi = require('joi')
const url = require('url')
const querystring = require('querystring')
const got = require('got')
const config = require('src/config/index.js')
const mod = {};
module.exports = mod;
const mod = {}
module.exports = mod
/**
* @return {string}
@@ -18,29 +17,29 @@ mod.getAuthURL = (state) => {
token_endpoint: joi.string().required(),
client_id: joi.string().required(),
client_secret: joi.string().required(),
state: joi.string().allow("", null).default(""),
state: joi.string().allow('', null).default('')
})
.unknown()
.validate({ ...config.sso, state });
if (input.error) throw new Error(input.error.message);
.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 { value } = input
const redirectUri = new url.URL("/oauth/redirect", config.server.url);
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;
scope: 'offline_access',
response_type: 'code',
redirect_uri: redirectUri.toString()
}
if (value.state) qs.state = state
return `${value.authorized_endpoint}?${querystring.stringify(qs)}`;
};
return `${value.authorized_endpoint}?${querystring.stringify(qs)}`
}
/**
* @return {string}
@@ -48,33 +47,116 @@ mod.getAuthURL = (state) => {
mod.getLogoutURL = () => {
const input = joi
.object({
logout_endpoint: joi.string().required(),
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);
.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() };
const qs = { state: 'logout', redirect_uri: redirectUri.toString() }
return `${input.value.logout_endpoint}?${querystring.stringify(qs)}`;
};
return `${input.value.logout_endpoint}?${querystring.stringify(qs)}`
}
/**
* @param {string} token
* @return {Promise<{username: string, display_name: string, email: string, groups: string[]}>}
*/
mod.getUserInfo = async (token) => {
const input = joi
.object()
/**
* @type {{
* client_id: string,
* userinfo_endpoint: string,
* token: string,
* }}
*/
const input = await joi
.object({
client_id: joi.string().required(),
client_secret: joi.string().required(),
userinfo_endpoint: joi.string().required(),
token: joi.string().required()
})
.unknown()
.validateAsync({ ...config.sso, token });
};
.validateAsync({ ...config.sso, token })
try {
const resp = await got.default.get(input.userinfo_endpoint, {
responseType: 'json',
headers: { Authorization: `Bearer ${input.token}` }
})
const body = await joi.object({
name: joi.string(),
email: joi.string().email().required(),
groups: joi.array().items(joi.string()).default([]),
family_name: joi.string(),
given_name: joi.string(),
preferred_username: joi.string()
}).unknown().validateAsync(resp.body)
const displayName = body.name || body.preferred_username || body.given_name || ''
return { display_name: displayName, email: body.email, groups: body.groups, username: body.preferred_username || '' }
} catch (err) {
console.log(err)
if (err instanceof got.HTTPError) {
if (err.code === 401) {
// try refresh token
return null
}
}
throw err
}
}
mod.refreshToken = async (token) => {
/**
* @type {{
* token_endpoint: string,
* client_id: string,
* client_secret: string,
* token: string,
* }}
*/
const input = await joi.object({
token_endpoint: joi.string().required(),
client_id: joi.string().required(),
client_secret: joi.string().required(),
token: joi.string().required()
}).unknown().validateAsync({ ...config.sso, token })
const qs = {
client_id: input.client_id,
client_secret: input.client_secret,
grant_type: 'refresh_token',
refresh_token: input.token
}
const resp = await got.default.post(input.token_endpoint, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: querystring.stringify(qs),
responseType: 'json'
})
const body = await joi.object({
access_token: joi.string().required(),
refresh_token: joi.string().required()
}).unknown().validateAsync(resp.body)
return { access_token: body.access_token, refresh_token: body.refresh_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
* @property {string[]} groups
*/
mod.getToken = async (code, state) => {
@@ -85,19 +167,19 @@ mod.getToken = async (code, state) => {
client_id: joi.string().required(),
client_secret: joi.string().required(),
state: joi.string().required(),
code: joi.string().required(),
code: joi.string().required()
})
.unknown()
.validate({ ...config.sso, state, code });
.validate({ ...config.sso, state, code })
if (input.error) throw new Error(input.error.message);
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 { value } = input
const redirectUri = new url.URL("/oauth/redirect", config.server.url);
const redirectUri = new url.URL('/oauth/redirect', config.server.url)
const qs = {
client_id: value.client_id,
@@ -105,57 +187,39 @@ mod.getToken = async (code, state) => {
redirect_uri: redirectUri.toString(),
code: value.code,
client_session_state: value.state,
grant_type: "authorization_code",
};
grant_type: 'authorization_code'
}
const resp = await got.default.post(value.token_endpoint, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Content-Type': 'application/x-www-form-urlencoded'
},
body: querystring.stringify(qs),
responseType: "json",
});
responseType: 'json'
})
const { body } = resp;
if (!body) throw new Error("resopnse body empty");
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");
refresh_token: refreshToken
} = body
// const decoded = jwt.decode(idToken);
// if (!decoded || typeof decoded !== "object")
// throw new Error("jwt decode fail");
// console.log("decoded ::: ", decoded);
const userInfo = await mod.getUserInfo(accessToken)
if (!userInfo) throw new Error('user info get fail')
const decoded = jwt.decode(accessToken);
// decode access token
console.log("token ::: ", jwt.decode(accessToken));
console.log('user info ::: ', userInfo)
<<<<<<< HEAD
console.log("body ::: ", body);
=======
const decoded = jwt.decode(idToken);
if (!decoded || typeof decoded !== 'object') throw new Error('jwt decode fail');
>>>>>>> c96cdf0ebd17f805235c6fa9eecf2ea79ecca19b
// @ts-ignore
const { preferred_username: preferredUsername } = decoded;
if (!preferredUsername) throw new Error("id token field missing");
const displayName = `${decoded.family_name ?? ""}${decoded.given_name ?? ""}`;
console.log('body ::: ', body)
/** @type {SSOAccount} */
const ssoAccount = {
access_token: accessToken,
refresh_token: refreshToken,
user_id: decoded.sub,
username: preferredUsername.toLowerCase(),
display_name: displayName ?? preferredUsername,
email: decoded.email ?? "",
};
...userInfo
}
return ssoAccount;
};
return ssoAccount
}