mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-03-21 17:24:28 -05:00
Merge 65c21d2504 into dff3511808
This commit is contained in:
commit
41b16e8f1e
|
|
@ -46,5 +46,32 @@
|
|||
"api_token": "api_token",
|
||||
"board_name": "board_name"
|
||||
},
|
||||
"api_base": "https://api.domain.com"
|
||||
"api_base": "https://api.domain.com",
|
||||
"discourse": {
|
||||
"sso": {
|
||||
"secret": "Discourse SSO secret"
|
||||
},
|
||||
"api": {
|
||||
"base_url": "https://discourse.example.com",
|
||||
"username": "system",
|
||||
"key": "Discourse API key"
|
||||
},
|
||||
"groups": {
|
||||
"access_level": {
|
||||
"1": "testers",
|
||||
"2": "juxt-moderators",
|
||||
"3": "developers"
|
||||
},
|
||||
"stripe_tier": {
|
||||
"1": "supporters-mario",
|
||||
"2": "supporters-super",
|
||||
"3": "supporters-mega"
|
||||
},
|
||||
"discord_role": {
|
||||
"1234567890123456789": "developers",
|
||||
"9876543210987654321": "discord-moderators",
|
||||
"1234567890987654321": "network-moderators"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -467,20 +467,7 @@ router.get('/sso/discourse', async (request, response, next) => {
|
|||
try {
|
||||
const accountData = await util.getUserAccountData(request, response);
|
||||
|
||||
// * Discourse REQUIRES unique emails, however we do not due to NN also
|
||||
// * not requiring unique email addresses. Email addresses, for now,
|
||||
// * are faked using the users PID. This will essentially disable email
|
||||
// * for the forum, but it's a bullet we have to bite for right now.
|
||||
// TODO - We can run our own SMTP server which maps fake emails (pid@pretendo.whatever) to users real emails
|
||||
const payload = Buffer.from(new URLSearchParams({
|
||||
nonce: decodedPayload.get('nonce'),
|
||||
external_id: accountData.pid,
|
||||
email: `${accountData.pid}@invalid.com`, // * Hack to get unique emails
|
||||
username: accountData.username,
|
||||
name: accountData.username,
|
||||
avatar_url: accountData.mii.image_url,
|
||||
avatar_force_update: true
|
||||
}).toString()).toString('base64');
|
||||
const payload = await util.createDiscoursePayload(decodedPayload.get('nonce'), accountData);
|
||||
|
||||
const query = new URLSearchParams({
|
||||
sso: payload,
|
||||
|
|
@ -551,20 +538,7 @@ router.post('/sso/discourse', async (request, response, next) => {
|
|||
|
||||
const accountData = await util.getUserAccountData(request, response);
|
||||
|
||||
// * Discourse REQUIRES unique emails, however we do not due to NN also
|
||||
// * not requiring unique email addresses. Email addresses, for now,
|
||||
// * are faked using the users PID. This will essentially disable email
|
||||
// * for the forum, but it's a bullet we have to bite for right now.
|
||||
// TODO - We can run our own SMTP server which maps fake emails (pid@pretendo.whatever) to users real emails
|
||||
const payload = Buffer.from(new URLSearchParams({
|
||||
nonce: decodedPayload.get('nonce'),
|
||||
external_id: accountData.pid,
|
||||
email: `${accountData.pid}@invalid.com`, // * Hack to get unique emails
|
||||
username: accountData.username,
|
||||
name: accountData.username,
|
||||
avatar_url: accountData.mii.image_url,
|
||||
avatar_force_update: true
|
||||
}).toString()).toString('base64');
|
||||
const payload = await util.createDiscoursePayload(decodedPayload.get('nonce'), accountData);
|
||||
|
||||
const query = new URLSearchParams({
|
||||
sso: payload,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ const PNIDSchema = new Schema({
|
|||
server_access_level: String,
|
||||
access_level: Number,
|
||||
username: String,
|
||||
mii: {
|
||||
name: String,
|
||||
image_url: String
|
||||
},
|
||||
connections: {
|
||||
discord: {
|
||||
id: String
|
||||
|
|
|
|||
|
|
@ -286,6 +286,15 @@ async function handleStripeEvent(event) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (await util.discourseUserExists(pid)) {
|
||||
const updatedPNID = await database.PNID.findOne({ pid });
|
||||
await util.syncDiscourseSso(updatedPNID);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Error syncing user Discourse SSO | ${pid} | - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
103
src/util.js
103
src/util.js
|
|
@ -230,6 +230,12 @@ function nintendoPasswordHash(password, pid) {
|
|||
return hashed;
|
||||
}
|
||||
|
||||
async function discordMemberHasRole(memberId, roleId) {
|
||||
const response = await discordRest.get(DiscordRoutes.guildMember(config.discord.guild_id, memberId));
|
||||
|
||||
return response.roles.includes(roleId);
|
||||
}
|
||||
|
||||
async function assignDiscordMemberSupporterRole(memberId, roleId) {
|
||||
if (memberId && memberId.trim() !== '') {
|
||||
await discordRest.put(DiscordRoutes.guildMemberRole(config.discord.guild_id, memberId, config.discord.roles.supporter));
|
||||
|
|
@ -256,10 +262,102 @@ async function removeDiscordMemberTesterRole(memberId) {
|
|||
}
|
||||
}
|
||||
|
||||
async function createDiscoursePayload(nonce, accountData) {
|
||||
const groups = config.discourse.groups;
|
||||
const managedGroups = Object.values(groups).flatMap(category => Object.values(category));
|
||||
const addGroups = [];
|
||||
|
||||
// * If more than one of the provided groups in add_groups are configured to
|
||||
// * be automatically set as the primary group, Discourse unfortunately
|
||||
// * appears to set the user's primary group arbitrarily and
|
||||
// * non-deterministically. However, it also ignores groups that the user
|
||||
// * was already in before this sign-in, so the primary group won't change
|
||||
// * if none of the user's group memberships change.
|
||||
if (accountData.connections.discord?.id) {
|
||||
for (const role in groups.discord_role) {
|
||||
if (await discordMemberHasRole(accountData.connections.discord.id, role)) {
|
||||
addGroups.push(groups.discord_role[role]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (accountData.connections.stripe?.tier_level) {
|
||||
for (const tier in groups.stripe_tier) {
|
||||
if (accountData.connections.stripe.tier_level.toString() === tier) {
|
||||
addGroups.push(groups.stripe_tier[tier]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const level in groups.access_level) {
|
||||
if (accountData.access_level.toString() === level) {
|
||||
addGroups.push(groups.access_level[level]);
|
||||
}
|
||||
}
|
||||
|
||||
const removeGroups = managedGroups.filter(group => !addGroups.includes(group));
|
||||
|
||||
// * Discourse SSO Payload
|
||||
// * https://meta.discourse.org/t/official-single-sign-on-for-discourse-sso/13045
|
||||
|
||||
// * Discourse REQUIRES unique emails, however we do not due to NN also
|
||||
// * not requiring unique email addresses. Email addresses, for now,
|
||||
// * are faked using the users PID. This will essentially disable email
|
||||
// * for the forum, but it's a bullet we have to bite for right now.
|
||||
// TODO - We can run our own SMTP server which maps fake emails (pid@pretendo.whatever) to users real emails
|
||||
return Buffer.from(new URLSearchParams({
|
||||
nonce: nonce,
|
||||
external_id: accountData.pid,
|
||||
email: `${accountData.pid}@invalid.com`, // * Hack to get unique emails
|
||||
username: accountData.username,
|
||||
name: accountData.mii.name,
|
||||
avatar_url: accountData.mii.image_url,
|
||||
avatar_force_update: true,
|
||||
add_groups: addGroups.join(','),
|
||||
remove_groups: removeGroups.join(',')
|
||||
}).toString()).toString('base64');
|
||||
}
|
||||
|
||||
function signDiscoursePayload(payload) {
|
||||
return crypto.createHmac('sha256', config.discourse.sso.secret).update(payload).digest('hex');
|
||||
}
|
||||
|
||||
async function discourseUserExists(pid) {
|
||||
const response = await got.get(`${config.discourse.api.base_url}/users/by-external/${pid}.json`, {
|
||||
throwHttpErrors: false,
|
||||
responseType: 'json'
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
return true;
|
||||
} else if (response.statusCode === 404) {
|
||||
return false;
|
||||
} else {
|
||||
throw new Error(`Discourse API error while checking if user ${pid} exists: ${response.statusCode} - ${JSON.stringify(response.body)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function syncDiscourseSso(pnid) {
|
||||
// * Documentation: https://meta.discourse.org/t/sync-discourseconnect-user-data-with-the-sync-sso-route/84398
|
||||
const headers = {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
'Api-Username': config.discourse.api.username,
|
||||
'Api-Key': config.discourse.api.key
|
||||
};
|
||||
|
||||
const payload = await createDiscoursePayload('', pnid);
|
||||
const post_data = {
|
||||
'sso': payload,
|
||||
'sig': signDiscoursePayload(payload)
|
||||
};
|
||||
|
||||
return got.post(`${config.discourse.api.base_url}/admin/users/sync_sso`, {
|
||||
headers: headers,
|
||||
form: post_data,
|
||||
responseType: 'json'
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fullUrl,
|
||||
getLocale,
|
||||
|
|
@ -280,5 +378,8 @@ module.exports = {
|
|||
assignDiscordMemberTesterRole,
|
||||
removeDiscordMemberSupporterRole,
|
||||
removeDiscordMemberTesterRole,
|
||||
signDiscoursePayload
|
||||
createDiscoursePayload,
|
||||
signDiscoursePayload,
|
||||
discourseUserExists,
|
||||
syncDiscourseSso
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user