mirror of
https://github.com/wekan/wekan.git
synced 2026-11-07 11:02:33 -05:00
New passwords: PASSWORD_MIN_LENGTH (default 8) and PASSWORD_CHECK_PWNED (off by default), in Admin Panel / People / Login and on every platform. The browser sends a password to Meteor hashed, so the server checks every password that reaches it as text - Admin Panel Set password and Add user, POST /users/register - and the sign-up, Change Password, reset and enrolment forms check in the browser. Signing in is never checked, so an existing shorter password keeps working until it is changed. A breached password is found with Have I Been Pwned's k-anonymity range API: only 5 hex digits of the SHA-1 leave WeKan, and an unreachable service never refuses a password. REQUIRE_2FA: off (default), admins or all. It covers accounts that sign in with a WeKan password; LDAP, OAuth2, SAML, CAS and Sandstorm accounts sign in through an identity provider and are not asked. Such an account can still sign in, sees only the 2FA set-up until it has 2FA, and until then the server refuses every method and publication but signing in and enrolling, HTTP routes do not accept its login and the REST API gives it no token. Once required, 2FA cannot be switched off. The new interface strings are pending translation. Thanks to xet7 !
72 lines
3.0 KiB
JavaScript
72 lines
3.0 KiB
JavaScript
import { Meteor } from 'meteor/meteor';
|
|
import { Accounts } from 'meteor/accounts-base';
|
|
|
|
// Shared by DDP and HTTP: possession of a valid token is insufficient when
|
|
// an administrator has disabled its owner.
|
|
export function allowActiveUser(user, source, req) {
|
|
if (!user) return false;
|
|
if (!user.loginDisabled) return true;
|
|
try {
|
|
require('/server/lib/securityLog').record({
|
|
key: 'authn.inactive', action: 'blocked', source, req,
|
|
userId: user._id, detail: 'Disabled account authentication refused',
|
|
});
|
|
} catch (_) { /* Logging must never change the authentication decision. */ }
|
|
return false;
|
|
}
|
|
|
|
// REQUIRE_2FA (models/lib/twoFactorRequirement.js): an account that has not
|
|
// set up the two-factor authentication it must have is not signed in over
|
|
// HTTP - files, exports, imports, the REST API. Not recorded in Admin Panel /
|
|
// Problems: an account between signing in and enrolling is ordinary use.
|
|
export function missingRequiredTwoFactor(user) {
|
|
const { requireTwoFactorMode, missingTwoFactor } = require('/models/lib/twoFactorRequirement');
|
|
const { authEnv } = require('/server/lib/authConfig');
|
|
return missingTwoFactor(user, requireTwoFactorMode(authEnv));
|
|
}
|
|
export async function activeUserById(id, source, req) {
|
|
if (typeof id !== 'string' || !id) return null;
|
|
const user = await Meteor.users.findOneAsync(id);
|
|
return allowActiveUser(user, source, req) && !missingRequiredTwoFactor(user) ? user : null;
|
|
}
|
|
|
|
export async function activeUserByToken(token, source, req) {
|
|
if (typeof token !== 'string' || !token) return null;
|
|
const user = await Meteor.users.findOneAsync({
|
|
'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(token),
|
|
});
|
|
return allowActiveUser(user, source, req) && !missingRequiredTwoFactor(user) ? user : null;
|
|
}
|
|
|
|
// Test the status in the same database operation that mints the token: a
|
|
// concurrent disable must not be followed by reinserting usable credentials.
|
|
export async function insertActiveLoginToken(userId, stampedToken) {
|
|
const result = await Meteor.users.rawCollection().updateOne(
|
|
{ _id: userId, loginDisabled: { $in: [false, null, ''] } },
|
|
{ $push: { 'services.resume.loginTokens': {
|
|
hashedToken: Accounts._hashLoginToken(stampedToken.token),
|
|
when: stampedToken.when,
|
|
} } },
|
|
);
|
|
if (!result.matchedCount) {
|
|
await activeUserById(userId, 'token-issuance');
|
|
throw new Meteor.Error(403, 'Invalid credentials');
|
|
}
|
|
}
|
|
|
|
export function revokeDisabledTokensModifier(modifier) {
|
|
if (modifier.$set?.loginDisabled) {
|
|
modifier.$set['services.resume.loginTokens'] = [];
|
|
// Avoid conflicting modifiers when a caller also touches the token array.
|
|
if (modifier.$push) delete modifier.$push['services.resume.loginTokens'];
|
|
if (modifier.$addToSet) delete modifier.$addToSet['services.resume.loginTokens'];
|
|
}
|
|
}
|
|
|
|
export async function revokeDisabledTokens(id) {
|
|
await Meteor.users.rawCollection().updateOne(
|
|
{ _id: id, loginDisabled: true },
|
|
{ $set: { 'services.resume.loginTokens': [] } },
|
|
);
|
|
}
|