mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-03-21 17:24:28 -05:00
feat: initial osTicket SSO implementation
This commit is contained in:
parent
a82e7317f3
commit
b8bafa19c5
94
package-lock.json
generated
94
package-lock.json
generated
|
|
@ -11,6 +11,7 @@
|
|||
"dependencies": {
|
||||
"@aws-sdk/client-ses": "^3.515.0",
|
||||
"@discordjs/rest": "^0.5.0",
|
||||
"@node-oauth/express-oauth-server": "^4.1.1",
|
||||
"@node-saml/node-saml": "^5.0.0",
|
||||
"@pretendonetwork/error-codes": "^1.0.3",
|
||||
"browserify": "^17.0.0",
|
||||
|
|
@ -30,6 +31,7 @@
|
|||
"mii-js": "github:PretendoNetwork/mii-js#v1.0.4",
|
||||
"mongoose": "^6.4.0",
|
||||
"morgan": "^1.10.0",
|
||||
"node-cache": "^5.1.2",
|
||||
"nodemailer": "^6.7.5",
|
||||
"stripe": "^9.9.0"
|
||||
},
|
||||
|
|
@ -1420,6 +1422,41 @@
|
|||
"sparse-bitfield": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-oauth/express-oauth-server": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@node-oauth/express-oauth-server/-/express-oauth-server-4.1.1.tgz",
|
||||
"integrity": "sha512-Ps6UFV7XAfggYvRpveoT3t3h4dluy9wViEgkSU1kBcTOhllpBNIKrlsHTC+hBd1PT/+SC7vzpIXd2uFrmwF8Vg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@node-oauth/oauth2-server": "^5.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-oauth/formats": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@node-oauth/formats/-/formats-1.0.0.tgz",
|
||||
"integrity": "sha512-DwSbLtdC8zC5B5gTJkFzJj5s9vr9SGzOgQvV9nH7tUVuMSScg0EswAczhjIapOmH3Y8AyP7C4Jv7b8+QJObWZA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@node-oauth/oauth2-server": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@node-oauth/oauth2-server/-/oauth2-server-5.2.0.tgz",
|
||||
"integrity": "sha512-tbw0aHPk1Pu/HmQlll4unYd+VHwoagbAmUBLys5g6hDh9khcKzTmE77Z0myMG5a66w3Yk3xBwCRPX9a7M+HTqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@node-oauth/formats": "1.0.0",
|
||||
"basic-auth": "2.0.1",
|
||||
"type-is": "1.6.18"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-saml/node-saml": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-5.0.0.tgz",
|
||||
|
|
@ -2764,6 +2801,15 @@
|
|||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/clone": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
||||
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/clone-response": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
|
||||
|
|
@ -5049,6 +5095,18 @@
|
|||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node_modules/node-cache": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
|
||||
"integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"clone": "2.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
|
|
@ -7695,6 +7753,29 @@
|
|||
"sparse-bitfield": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"@node-oauth/express-oauth-server": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@node-oauth/express-oauth-server/-/express-oauth-server-4.1.1.tgz",
|
||||
"integrity": "sha512-Ps6UFV7XAfggYvRpveoT3t3h4dluy9wViEgkSU1kBcTOhllpBNIKrlsHTC+hBd1PT/+SC7vzpIXd2uFrmwF8Vg==",
|
||||
"requires": {
|
||||
"@node-oauth/oauth2-server": "^5.2.0"
|
||||
}
|
||||
},
|
||||
"@node-oauth/formats": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@node-oauth/formats/-/formats-1.0.0.tgz",
|
||||
"integrity": "sha512-DwSbLtdC8zC5B5gTJkFzJj5s9vr9SGzOgQvV9nH7tUVuMSScg0EswAczhjIapOmH3Y8AyP7C4Jv7b8+QJObWZA=="
|
||||
},
|
||||
"@node-oauth/oauth2-server": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@node-oauth/oauth2-server/-/oauth2-server-5.2.0.tgz",
|
||||
"integrity": "sha512-tbw0aHPk1Pu/HmQlll4unYd+VHwoagbAmUBLys5g6hDh9khcKzTmE77Z0myMG5a66w3Yk3xBwCRPX9a7M+HTqA==",
|
||||
"requires": {
|
||||
"@node-oauth/formats": "1.0.0",
|
||||
"basic-auth": "2.0.1",
|
||||
"type-is": "1.6.18"
|
||||
}
|
||||
},
|
||||
"@node-saml/node-saml": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-5.0.0.tgz",
|
||||
|
|
@ -8780,6 +8861,11 @@
|
|||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"clone": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
||||
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
|
||||
},
|
||||
"clone-response": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
|
||||
|
|
@ -10509,6 +10595,14 @@
|
|||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node-cache": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
|
||||
"integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
|
||||
"requires": {
|
||||
"clone": "2.x"
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
"dependencies": {
|
||||
"@aws-sdk/client-ses": "^3.515.0",
|
||||
"@discordjs/rest": "^0.5.0",
|
||||
"@node-oauth/express-oauth-server": "^4.1.1",
|
||||
"@node-saml/node-saml": "^5.0.0",
|
||||
"@pretendonetwork/error-codes": "^1.0.3",
|
||||
"browserify": "^17.0.0",
|
||||
|
|
@ -42,6 +43,7 @@
|
|||
"mii-js": "github:PretendoNetwork/mii-js#v1.0.4",
|
||||
"mongoose": "^6.4.0",
|
||||
"morgan": "^1.10.0",
|
||||
"node-cache": "^5.1.2",
|
||||
"nodemailer": "^6.7.5",
|
||||
"stripe": "^9.9.0"
|
||||
},
|
||||
|
|
|
|||
141
src/routes/oauth.js
Normal file
141
src/routes/oauth.js
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
const express = require('express');
|
||||
const OAuthServer = require('@node-oauth/express-oauth-server');
|
||||
const NodeCache = require('node-cache');
|
||||
|
||||
const config = require('../../config.json');
|
||||
const util = require('../util');
|
||||
|
||||
// * osTicket uses the authorization code grant type and requests a new
|
||||
// * authorization code and access token for each sign-in
|
||||
const authorizationCodeLifetime = 2 * 60;
|
||||
const accessTokenLifetime = 2 * 60;
|
||||
const authorizationCodeCache = new NodeCache({
|
||||
stdTTL: authorizationCodeLifetime,
|
||||
checkperiod: authorizationCodeLifetime / 2
|
||||
});
|
||||
const accessTokenCache = new NodeCache({
|
||||
stdTTL: accessTokenLifetime,
|
||||
checkperiod: accessTokenLifetime / 2
|
||||
});
|
||||
|
||||
const router = new express.Router();
|
||||
|
||||
async function getClient(clientId, clientSecret) {
|
||||
console.log(`getClient(clientId: ${JSON.stringify(clientId, null, 2)}, clientSecret: ${JSON.stringify(clientSecret, null, 2)})`);
|
||||
if (clientId === config.osticket.oauth.client_id &&
|
||||
(!clientSecret || clientSecret === config.osticket.oauth.client_secret)) {
|
||||
return {
|
||||
id: clientId,
|
||||
redirectUris: [config.osticket.oauth.redirect_uri],
|
||||
grants: ['authorization_code']
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAuthorizationCode(code, client, user) {
|
||||
console.log(`saveAuthorizationCode(code: ${JSON.stringify(code, null, 2)}, client: ${JSON.stringify(client, null, 2)}, user: ${JSON.stringify(user, null, 2)})`);
|
||||
const authorizationCodeData = {
|
||||
authorizationCode: code.authorizationCode,
|
||||
expiresAt: new Date(Date.now() + authorizationCodeLifetime * 1000),
|
||||
redirectUri: code.redirectUri,
|
||||
scope: code.scope,
|
||||
client: client,
|
||||
user: user
|
||||
};
|
||||
|
||||
authorizationCodeCache.set(code.authorizationCode, authorizationCodeData);
|
||||
|
||||
return authorizationCodeData;
|
||||
}
|
||||
|
||||
async function getAuthorizationCode(authorizationCode) {
|
||||
console.log(`getAuthorizationCode(authorizationCode: ${JSON.stringify(authorizationCode, null, 2)})`);
|
||||
const authorizationCodeData = authorizationCodeCache.get(authorizationCode);
|
||||
|
||||
if (authorizationCodeData) {
|
||||
return authorizationCodeData;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
async function revokeAuthorizationCode(code) {
|
||||
console.log(`revokeAuthorizationCode(code: ${JSON.stringify(code, null, 2)})`);
|
||||
const deletedKeys = authorizationCodeCache.del(code.authorizationCode);
|
||||
|
||||
return deletedKeys > 0;
|
||||
}
|
||||
|
||||
async function saveToken(token, client, user) {
|
||||
console.log(`saveToken(token: ${JSON.stringify(token, null, 2)}, client: ${JSON.stringify(client, null, 2)}, user: ${JSON.stringify(user, null, 2)})`);
|
||||
// * osTicket doesn't use refresh tokens
|
||||
const accessTokenData = {
|
||||
accessToken: token.accessToken,
|
||||
accessTokenExpiresAt: new Date(Date.now() + accessTokenLifetime * 1000),
|
||||
scope: token.scope,
|
||||
client: client,
|
||||
user: user
|
||||
};
|
||||
|
||||
accessTokenCache.set(token.accessToken, accessTokenData);
|
||||
|
||||
return accessTokenData;
|
||||
}
|
||||
|
||||
|
||||
async function getAccessToken(accessToken) {
|
||||
console.log(`getAccessToken(accessToken: ${JSON.stringify(accessToken, null, 2)})`);
|
||||
const accessTokenData = accessTokenCache.get(accessToken);
|
||||
|
||||
if (accessTokenData) {
|
||||
return accessTokenData;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAuthentication(request, response) {
|
||||
console.log('handleAuthentication(request, response)');
|
||||
if (request.cookies.access_token && request.cookies.refresh_token) {
|
||||
try {
|
||||
const accountData = await util.getUserAccountData(request, response);
|
||||
|
||||
// * Use the same fake email address as Discourse SSO for forwarding
|
||||
return {
|
||||
username: accountData.username,
|
||||
email: `${accountData.pid}@forward.local`, //TODO: Choose a domain
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const oauth = new OAuthServer({
|
||||
model: {
|
||||
getClient,
|
||||
saveAuthorizationCode,
|
||||
getAuthorizationCode,
|
||||
revokeAuthorizationCode,
|
||||
saveToken,
|
||||
getAccessToken
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
router.use('/authorize', oauth.authorize({
|
||||
authenticateHandler: {
|
||||
handle: handleAuthentication
|
||||
}
|
||||
}));
|
||||
router.use('/token', oauth.token());
|
||||
router.get('/details', oauth.authenticate(), (request, response) => {
|
||||
response.json(response.locals.oauth.token.user);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -99,6 +99,7 @@ const routes = {
|
|||
docs: require('./routes/docs'),
|
||||
progress: require('./routes/progress'),
|
||||
account: require('./routes/account'),
|
||||
oauth: require('./routes/oauth'),
|
||||
blog: require('./routes/blog'),
|
||||
localization: require('./routes/localization'),
|
||||
aprilfools: require('./routes/aprilfools')
|
||||
|
|
@ -109,6 +110,7 @@ app.use('/faq', routes.faq);
|
|||
app.use('/docs', routes.docs);
|
||||
app.use('/progress', routes.progress);
|
||||
app.use('/account', routes.account);
|
||||
app.use('/oauth', routes.oauth);
|
||||
app.use('/localization', routes.localization);
|
||||
app.use('/blog', routes.blog);
|
||||
app.use('/nso-legacy-pack', routes.aprilfools);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user