mirror of
https://github.com/plankanban/planka.git
synced 2025-07-19 05:09:43 +02:00
parent
1dfdd8f6c7
commit
fca77c02b3
7 changed files with 46 additions and 29 deletions
|
@ -37,6 +37,7 @@ services:
|
||||||
# - OIDC_RESPONSE_MODE=fragment
|
# - OIDC_RESPONSE_MODE=fragment
|
||||||
# - OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
# - OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
||||||
# - OIDC_ADMIN_ROLES=admin
|
# - OIDC_ADMIN_ROLES=admin
|
||||||
|
# - OIDC_CLAIMS_SOURCE=userinfo
|
||||||
# - OIDC_EMAIL_ATTRIBUTE=email
|
# - OIDC_EMAIL_ATTRIBUTE=email
|
||||||
# - OIDC_NAME_ATTRIBUTE=name
|
# - OIDC_NAME_ATTRIBUTE=name
|
||||||
# - OIDC_USERNAME_ATTRIBUTE=preferred_username
|
# - OIDC_USERNAME_ATTRIBUTE=preferred_username
|
||||||
|
|
|
@ -44,6 +44,7 @@ services:
|
||||||
# - OIDC_RESPONSE_MODE=fragment
|
# - OIDC_RESPONSE_MODE=fragment
|
||||||
# - OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
# - OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
||||||
# - OIDC_ADMIN_ROLES=admin
|
# - OIDC_ADMIN_ROLES=admin
|
||||||
|
# - OIDC_CLAIMS_SOURCE=userinfo
|
||||||
# - OIDC_EMAIL_ATTRIBUTE=email
|
# - OIDC_EMAIL_ATTRIBUTE=email
|
||||||
# - OIDC_NAME_ATTRIBUTE=name
|
# - OIDC_NAME_ATTRIBUTE=name
|
||||||
# - OIDC_USERNAME_ATTRIBUTE=preferred_username
|
# - OIDC_USERNAME_ATTRIBUTE=preferred_username
|
||||||
|
|
|
@ -35,6 +35,7 @@ SECRET_KEY=notsecretkey
|
||||||
# OIDC_RESPONSE_MODE=fragment
|
# OIDC_RESPONSE_MODE=fragment
|
||||||
# OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
# OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
||||||
# OIDC_ADMIN_ROLES=admin
|
# OIDC_ADMIN_ROLES=admin
|
||||||
|
# OIDC_CLAIMS_SOURCE=userinfo
|
||||||
# OIDC_EMAIL_ATTRIBUTE=email
|
# OIDC_EMAIL_ATTRIBUTE=email
|
||||||
# OIDC_NAME_ATTRIBUTE=name
|
# OIDC_NAME_ATTRIBUTE=name
|
||||||
# OIDC_USERNAME_ATTRIBUTE=preferred_username
|
# OIDC_USERNAME_ATTRIBUTE=preferred_username
|
||||||
|
|
|
@ -6,8 +6,8 @@ const Errors = {
|
||||||
INVALID_CODE_OR_NONCE: {
|
INVALID_CODE_OR_NONCE: {
|
||||||
invalidCodeOrNonce: 'Invalid code or nonce',
|
invalidCodeOrNonce: 'Invalid code or nonce',
|
||||||
},
|
},
|
||||||
INVALID_USERINFO_SIGNATURE: {
|
INVALID_USERINFO_CONFIGURATION: {
|
||||||
invalidUserinfoSignature: 'Invalid signature on userinfo due to client misconfiguration',
|
invalidUserinfoConfiguration: 'Invalid userinfo configuration',
|
||||||
},
|
},
|
||||||
EMAIL_ALREADY_IN_USE: {
|
EMAIL_ALREADY_IN_USE: {
|
||||||
emailAlreadyInUse: 'Email already in use',
|
emailAlreadyInUse: 'Email already in use',
|
||||||
|
@ -40,7 +40,7 @@ module.exports = {
|
||||||
invalidCodeOrNonce: {
|
invalidCodeOrNonce: {
|
||||||
responseType: 'unauthorized',
|
responseType: 'unauthorized',
|
||||||
},
|
},
|
||||||
invalidUserinfoSignature: {
|
invalidUserinfoConfiguration: {
|
||||||
responseType: 'unauthorized',
|
responseType: 'unauthorized',
|
||||||
},
|
},
|
||||||
emailAlreadyInUse: {
|
emailAlreadyInUse: {
|
||||||
|
@ -63,7 +63,7 @@ module.exports = {
|
||||||
sails.log.warn(`Invalid code or nonce! (IP: ${remoteAddress})`);
|
sails.log.warn(`Invalid code or nonce! (IP: ${remoteAddress})`);
|
||||||
return Errors.INVALID_CODE_OR_NONCE;
|
return Errors.INVALID_CODE_OR_NONCE;
|
||||||
})
|
})
|
||||||
.intercept('invalidUserinfoSignature', () => Errors.INVALID_USERINFO_SIGNATURE)
|
.intercept('invalidUserinfoConfiguration', () => Errors.INVALID_USERINFO_CONFIGURATION)
|
||||||
.intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE)
|
.intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE)
|
||||||
.intercept('usernameAlreadyInUse', () => Errors.USERNAME_ALREADY_IN_USE)
|
.intercept('usernameAlreadyInUse', () => Errors.USERNAME_ALREADY_IN_USE)
|
||||||
.intercept('missingValues', () => Errors.MISSING_VALUES);
|
.intercept('missingValues', () => Errors.MISSING_VALUES);
|
||||||
|
|
|
@ -12,7 +12,7 @@ module.exports = {
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
invalidCodeOrNonce: {},
|
invalidCodeOrNonce: {},
|
||||||
invalidUserinfoSignature: {},
|
invalidUserinfoConfiguration: {},
|
||||||
missingValues: {},
|
missingValues: {},
|
||||||
emailAlreadyInUse: {},
|
emailAlreadyInUse: {},
|
||||||
usernameAlreadyInUse: {},
|
usernameAlreadyInUse: {},
|
||||||
|
@ -21,9 +21,9 @@ module.exports = {
|
||||||
async fn(inputs) {
|
async fn(inputs) {
|
||||||
const client = sails.hooks.oidc.getClient();
|
const client = sails.hooks.oidc.getClient();
|
||||||
|
|
||||||
let userInfo;
|
let tokenSet;
|
||||||
try {
|
try {
|
||||||
const tokenSet = await client.callback(
|
tokenSet = await client.callback(
|
||||||
sails.config.custom.oidcRedirectUri,
|
sails.config.custom.oidcRedirectUri,
|
||||||
{
|
{
|
||||||
iss: sails.config.custom.oidcIssuer,
|
iss: sails.config.custom.oidcIssuer,
|
||||||
|
@ -33,23 +33,36 @@ module.exports = {
|
||||||
nonce: inputs.nonce,
|
nonce: inputs.nonce,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
userInfo = await client.userinfo(tokenSet);
|
} catch (error) {
|
||||||
} catch (e) {
|
sails.log.warn(`Error while exchanging OIDC code: ${error}`);
|
||||||
if (
|
|
||||||
e instanceof SyntaxError &&
|
|
||||||
e.message.includes('Unexpected token e in JSON at position 0')
|
|
||||||
) {
|
|
||||||
sails.log.warn('Error while exchanging OIDC code: userinfo response is signed');
|
|
||||||
throw 'invalidUserinfoSignature';
|
|
||||||
}
|
|
||||||
|
|
||||||
sails.log.warn(`Error while exchanging OIDC code: ${e}`);
|
|
||||||
throw 'invalidCodeOrNonce';
|
throw 'invalidCodeOrNonce';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let claims;
|
||||||
|
if (sails.config.custom.oidcClaimsSource === 'id_token') {
|
||||||
|
claims = tokenSet.claims();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
claims = await client.userinfo(tokenSet);
|
||||||
|
} catch (error) {
|
||||||
|
let errorText;
|
||||||
|
if (
|
||||||
|
error instanceof SyntaxError &&
|
||||||
|
error.message.includes('Unexpected token e in JSON at position 0')
|
||||||
|
) {
|
||||||
|
errorText = 'response is signed';
|
||||||
|
} else {
|
||||||
|
errorText = error.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
sails.log.warn(`Error while fetching OIDC userinfo: ${errorText}`);
|
||||||
|
throw 'invalidUserinfoConfiguration';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!userInfo[sails.config.custom.oidcEmailAttribute] ||
|
!claims[sails.config.custom.oidcEmailAttribute] ||
|
||||||
!userInfo[sails.config.custom.oidcNameAttribute]
|
!claims[sails.config.custom.oidcNameAttribute]
|
||||||
) {
|
) {
|
||||||
throw 'missingValues';
|
throw 'missingValues';
|
||||||
}
|
}
|
||||||
|
@ -58,23 +71,23 @@ module.exports = {
|
||||||
if (sails.config.custom.oidcAdminRoles.includes('*')) {
|
if (sails.config.custom.oidcAdminRoles.includes('*')) {
|
||||||
isAdmin = true;
|
isAdmin = true;
|
||||||
} else {
|
} else {
|
||||||
const roles = userInfo[sails.config.custom.oidcRolesAttribute];
|
const roles = claims[sails.config.custom.oidcRolesAttribute];
|
||||||
if (Array.isArray(roles)) {
|
if (Array.isArray(roles)) {
|
||||||
// Use a Set here to avoid quadratic time complexity
|
// Use a Set here to avoid quadratic time complexity
|
||||||
const userRoles = new Set(userInfo[sails.config.custom.oidcRolesAttribute]);
|
const userRoles = new Set(claims[sails.config.custom.oidcRolesAttribute]);
|
||||||
isAdmin = sails.config.custom.oidcAdminRoles.findIndex((role) => userRoles.has(role)) > -1;
|
isAdmin = sails.config.custom.oidcAdminRoles.findIndex((role) => userRoles.has(role)) > -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = {
|
const values = {
|
||||||
isAdmin,
|
isAdmin,
|
||||||
email: userInfo[sails.config.custom.oidcEmailAttribute],
|
email: claims[sails.config.custom.oidcEmailAttribute],
|
||||||
isSso: true,
|
isSso: true,
|
||||||
name: userInfo[sails.config.custom.oidcNameAttribute],
|
name: claims[sails.config.custom.oidcNameAttribute],
|
||||||
subscribeToOwnCards: false,
|
subscribeToOwnCards: false,
|
||||||
};
|
};
|
||||||
if (!sails.config.custom.oidcIgnoreUsername) {
|
if (!sails.config.custom.oidcIgnoreUsername) {
|
||||||
values.username = userInfo[sails.config.custom.oidcUsernameAttribute];
|
values.username = claims[sails.config.custom.oidcUsernameAttribute];
|
||||||
}
|
}
|
||||||
|
|
||||||
let user;
|
let user;
|
||||||
|
@ -84,7 +97,7 @@ module.exports = {
|
||||||
// concurrently with logging in via OIDC.
|
// concurrently with logging in via OIDC.
|
||||||
let identityProviderUser = await IdentityProviderUser.findOne({
|
let identityProviderUser = await IdentityProviderUser.findOne({
|
||||||
issuer: sails.config.custom.oidcIssuer,
|
issuer: sails.config.custom.oidcIssuer,
|
||||||
sub: userInfo.sub,
|
sub: claims.sub,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (identityProviderUser) {
|
if (identityProviderUser) {
|
||||||
|
@ -108,7 +121,7 @@ module.exports = {
|
||||||
identityProviderUser = await IdentityProviderUser.create({
|
identityProviderUser = await IdentityProviderUser.create({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
issuer: sails.config.custom.oidcIssuer,
|
issuer: sails.config.custom.oidcIssuer,
|
||||||
sub: userInfo.sub,
|
sub: claims.sub,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -130,8 +130,8 @@ async function sendWebhook(webhook, event, data, user) {
|
||||||
`Webhook ${webhook.url} failed with status ${response.status} and message: ${message}`,
|
`Webhook ${webhook.url} failed with status ${response.status} and message: ${message}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
sails.log.error(`Webhook ${webhook.url} failed with error message: ${e.message}`);
|
sails.log.error(`Webhook ${webhook.url} failed with error: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ module.exports.custom = {
|
||||||
oidcResponseMode: process.env.OIDC_RESPONSE_MODE || 'fragment',
|
oidcResponseMode: process.env.OIDC_RESPONSE_MODE || 'fragment',
|
||||||
oidcUseDefaultResponseMode: process.env.OIDC_USE_DEFAULT_RESPONSE_MODE === 'true',
|
oidcUseDefaultResponseMode: process.env.OIDC_USE_DEFAULT_RESPONSE_MODE === 'true',
|
||||||
oidcAdminRoles: process.env.OIDC_ADMIN_ROLES ? process.env.OIDC_ADMIN_ROLES.split(',') : [],
|
oidcAdminRoles: process.env.OIDC_ADMIN_ROLES ? process.env.OIDC_ADMIN_ROLES.split(',') : [],
|
||||||
|
oidcClaimsSource: process.env.OIDC_CLAIMS_SOURCE || 'userinfo',
|
||||||
oidcEmailAttribute: process.env.OIDC_EMAIL_ATTRIBUTE || 'email',
|
oidcEmailAttribute: process.env.OIDC_EMAIL_ATTRIBUTE || 'email',
|
||||||
oidcNameAttribute: process.env.OIDC_NAME_ATTRIBUTE || 'name',
|
oidcNameAttribute: process.env.OIDC_NAME_ATTRIBUTE || 'name',
|
||||||
oidcUsernameAttribute: process.env.OIDC_USERNAME_ATTRIBUTE || 'preferred_username',
|
oidcUsernameAttribute: process.env.OIDC_USERNAME_ATTRIBUTE || 'preferred_username',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue