2023-10-17 19:18:19 +02:00
|
|
|
module.exports = {
|
|
|
|
inputs: {
|
2023-10-19 14:39:21 +02:00
|
|
|
code: {
|
|
|
|
type: 'string',
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
nonce: {
|
2023-10-17 19:18:19 +02:00
|
|
|
type: 'string',
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
exits: {
|
|
|
|
invalidToken: {},
|
|
|
|
missingValues: {},
|
|
|
|
emailAlreadyInUse: {},
|
|
|
|
usernameAlreadyInUse: {},
|
|
|
|
},
|
|
|
|
|
|
|
|
async fn(inputs) {
|
2023-10-19 14:39:21 +02:00
|
|
|
const client = sails.hooks.oidc.getClient();
|
2023-10-17 19:18:19 +02:00
|
|
|
|
2023-10-19 14:39:21 +02:00
|
|
|
let userInfo;
|
|
|
|
try {
|
|
|
|
const tokenSet = await client.callback(
|
|
|
|
`${sails.config.custom.baseUrl}/oidc-callback`,
|
|
|
|
{ code: inputs.code },
|
|
|
|
{ nonce: inputs.nonce },
|
|
|
|
);
|
|
|
|
userInfo = await client.userinfo(tokenSet);
|
|
|
|
} catch (e) {
|
|
|
|
sails.log.warn(`Error while exchanging OIDC code: ${e}`);
|
2023-10-17 19:18:19 +02:00
|
|
|
throw 'invalidToken';
|
|
|
|
}
|
|
|
|
|
2023-10-19 14:39:21 +02:00
|
|
|
if (!userInfo.email || !userInfo.name) {
|
2023-10-17 19:18:19 +02:00
|
|
|
throw 'missingValues';
|
|
|
|
}
|
|
|
|
|
|
|
|
let isAdmin = false;
|
|
|
|
if (sails.config.custom.oidcAdminRoles.includes('*')) {
|
|
|
|
isAdmin = true;
|
|
|
|
} else {
|
2023-10-19 14:39:21 +02:00
|
|
|
const roles = userInfo[sails.config.custom.oidcRolesAttribute];
|
2023-10-17 19:18:19 +02:00
|
|
|
if (Array.isArray(roles)) {
|
2023-10-19 14:39:21 +02:00
|
|
|
// Use a Set here to avoid quadratic time complexity
|
|
|
|
const userRoles = new Set(userInfo[sails.config.custom.oidcRolesAttribute]);
|
|
|
|
isAdmin = sails.config.custom.oidcAdminRoles.findIndex((role) => userRoles.has(role)) > -1;
|
2023-10-17 19:18:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const values = {
|
|
|
|
isAdmin,
|
2023-10-19 14:39:21 +02:00
|
|
|
email: userInfo.email,
|
2023-10-17 19:18:19 +02:00
|
|
|
isSso: true,
|
2023-10-19 14:39:21 +02:00
|
|
|
name: userInfo.name,
|
|
|
|
username: userInfo.preferred_username,
|
2023-10-17 19:18:19 +02:00
|
|
|
subscribeToOwnCards: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
let user;
|
2023-10-19 14:39:21 +02:00
|
|
|
// This whole block technically needs to be executed in a transaction
|
|
|
|
// with SERIALIZABLE isolation level (but Waterline does not support
|
|
|
|
// that), so this will result in errors if for example users are deleted
|
|
|
|
// concurrently with logging in via OIDC.
|
|
|
|
let identityProviderUser = await IdentityProviderUser.findOne({
|
|
|
|
issuer: sails.config.custom.oidcIssuer,
|
|
|
|
sub: userInfo.sub,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (identityProviderUser) {
|
|
|
|
user = await sails.helpers.users.getOne(identityProviderUser.userId);
|
|
|
|
} else {
|
|
|
|
// If no IDP/User mapping exists, search for the user by email.
|
|
|
|
user = await sails.helpers.users.getOne({
|
|
|
|
email: values.email,
|
2023-10-17 19:18:19 +02:00
|
|
|
});
|
|
|
|
|
2023-10-19 14:39:21 +02:00
|
|
|
// Otherwise, create a new user.
|
|
|
|
if (!user) {
|
|
|
|
user = await sails.helpers.users
|
|
|
|
.createOne(values)
|
|
|
|
.intercept('usernameAlreadyInUse', 'usernameAlreadyInUse');
|
2023-10-17 19:18:19 +02:00
|
|
|
}
|
|
|
|
|
2023-10-19 14:39:21 +02:00
|
|
|
identityProviderUser = await IdentityProviderUser.create({
|
|
|
|
userId: user.id,
|
|
|
|
issuer: sails.config.custom.oidcIssuer,
|
|
|
|
sub: userInfo.sub,
|
|
|
|
});
|
2023-10-17 19:18:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const updateFieldKeys = ['email', 'isAdmin', 'isSso', 'name', 'username'];
|
|
|
|
|
2023-10-19 14:39:21 +02:00
|
|
|
const updateValues = {};
|
|
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
|
|
for (const k of updateFieldKeys) {
|
|
|
|
if (values[k] !== user[k]) updateValues[k] = values[k];
|
|
|
|
}
|
2023-10-17 19:18:19 +02:00
|
|
|
|
|
|
|
if (Object.keys(updateValues).length > 0) {
|
|
|
|
user = await sails.helpers.users
|
|
|
|
.updateOne(user, updateValues, {}) // FIXME: hack for last parameter
|
|
|
|
.intercept('emailAlreadyInUse', 'emailAlreadyInUse')
|
|
|
|
.intercept('usernameAlreadyInUse', 'usernameAlreadyInUse');
|
|
|
|
}
|
|
|
|
|
|
|
|
return user;
|
|
|
|
},
|
|
|
|
};
|