Restrict SSO registration

This commit is contained in:
yflory 2023-12-19 17:59:56 +01:00
parent e52d9f77c1
commit e65baebadc
10 changed files with 111 additions and 30 deletions

View File

@ -49,9 +49,11 @@ define([
Exports.ssoAuth = function (provider, cb) {
var keys = Nacl.sign.keyPair();
var inviteToken = window.location.hash.slice(1);
localStorage.CP_sso_auth = JSON.stringify({
s: Nacl.util.encodeBase64(keys.secretKey),
p: Nacl.util.encodeBase64(keys.publicKey)
p: Nacl.util.encodeBase64(keys.publicKey),
token: inviteToken
});
ServerCommand(keys, {
command: 'SSO_AUTH',

View File

@ -56,6 +56,7 @@ define([
h('h2', Msg.register_notes_title),
Pages.setHTML(h('div.cp-register-notes'), Msg.register_notes)
]),
h('div.col-md-3.cp-closed-filler'+ssoEnabled, h('div')),
h('div.cp-reg-form.col-md-6', [
h('div#userForm.form-group'+ssoEnforced, [
h('div.cp-register-instance', [
@ -105,6 +106,7 @@ define([
h('div.cp-register-sso', Msg.sso_register_description)
]),
]),
h('div.col-md-3.cp-closed-filler'+ssoEnabled),
])
]);
};

View File

@ -22,6 +22,9 @@
.cp-restricted-registration {
display: none;
}
.cp-closed-filler {
display: none;
}
}
&.cp-register-closed {
div.cp-register-det {

View File

@ -461,6 +461,8 @@ var setLastEviction = function (Env, Server, cb, data, unsafeKey) {
var instanceStatus = function (Env, Server, cb) {
cb(void 0, {
restrictRegistration: Env.restrictRegistration,
restrictSsoRegistration: Env.restrictSsoRegistration,
enableEmbedding: Env.enableEmbedding,
launchTime: Env.launchTime,
currentTime: +new Date(),

View File

@ -111,7 +111,7 @@ Block.validateAncestorProof = function (Env, proof, _cb) {
Block.writeLoginBlock = function (Env, msg, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
const { publicKey, signature, ciphertext, registrationProof, userData, inviteToken } = msg;
const { publicKey, signature, ciphertext, registrationProof, userData, inviteToken, isSSO } = msg;
var previousKey;
var validatedBlock, path;
@ -123,9 +123,11 @@ Block.writeLoginBlock = function (Env, msg, _cb) {
if (err || !state) { return; } // Invalid token, don't abort, check registration proof
validatedInvite = true;
}));
}).nThen(function (w) {
}).nThen(function (w) {
if (!Env.restrictRegistration) { return; }
if (!registrationProof && !validatedInvite) {
var ssoAllowed = isSSO && !Env.restrictSsoRegistration
if (!(registrationProof || validatedInvite || ssoAllowed)) {
// we allow users with existing blocks to create new ones
// call back with error if registration is restricted and no proof of an existing block was provided
w.abort();
@ -134,9 +136,9 @@ Block.writeLoginBlock = function (Env, msg, _cb) {
});
return cb("E_RESTRICTED");
}
if (!registrationProof) { return; }
Block.validateAncestorProof(Env, registrationProof, w(function (err, provenKey) {
if (err || !provenKey) { // double check that a key was validated
if (validatedInvite) { return; }
w.abort();
Env.Log.warn('BLOCK_REJECTED_INVALID_ANCESTOR', {
error: err,

View File

@ -10,6 +10,7 @@ var Core = require("./commands/core");
IMPLEMENTED:
RESTRICT_REGISTRATION(<boolean>)
RESTRICT_SSO_REGISTRATION(<boolean>)
UPDATE_DEFAULT_STORAGE(<number>)
// QUOTA MANAGEMENT
@ -112,6 +113,7 @@ commands.ENFORCE_MFA = makeBooleanSetter('enforceMFA');
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['RESTRICT_REGISTRATION', [true]]], console.log)
commands.RESTRICT_REGISTRATION = makeBooleanSetter('restrictRegistration');
commands.RESTRICT_SSO_REGISTRATION = makeBooleanSetter('restrictSsoRegistration');
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['DISABLE_INTEGRATED_EVICTION', [true]]], console.log)
commands.DISABLE_INTEGRATED_EVICTION = makeBooleanSetter('disableIntegratedEviction');

View File

@ -579,6 +579,7 @@ var serveConfig = makeRouteCache(function () {
maxUploadSize: Env.maxUploadSize,
premiumUploadSize: Env.premiumUploadSize,
restrictRegistration: Env.restrictRegistration,
restrictSsoRegistration: Env.restrictSsoRegistration,
httpSafeOrigin: Env.httpSafeOrigin,
enableEmbedding: Env.enableEmbedding,
fileHost: Env.fileHost,

View File

@ -1495,29 +1495,81 @@ Example
};
// Msg.admin_registrationHint, .admin_registrationTitle
create['registration'] = makeAdminCheckbox({
key: 'registration',
getState: function () {
return APP.instanceStatus.restrictRegistration;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['RESTRICT_REGISTRATION', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.restrictRegistration);
flushCacheNotice();
// Msg.admin_registrationSsoTitle
Messages.admin_registrationSsoTitle = "ALSO CLOSE SSO REGISTRATION"; // XXX
create['registration'] = function () {
var key = 'registration';
var refresh = function () {};
var $div = makeAdminCheckbox({
key: 'registration',
getState: function () {
return APP.instanceStatus.restrictRegistration;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['RESTRICT_REGISTRATION', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.restrictRegistration);
refresh();
flushCacheNotice();
});
});
});
},
});
}
})();
var $sso = makeAdminCheckbox({
key: 'registration-sso',
getState: function () {
return APP.instanceStatus.restrictSsoRegistration;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['RESTRICT_SSO_REGISTRATION', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.restrictSsoRegistration);
flushCacheNotice();
});
});
}
})();
var ssoEnabled = ApiConfig.sso && ApiConfig.sso.list && ApiConfig.sso.list.length;
if (ssoEnabled) {
$sso.find('#cp-admin-registration-sso').hide();
$sso.find('> span.cp-sidebarlayout-description').hide();
$div.append($sso);
}
refresh = () => {
var closed = APP.instanceStatus.restrictRegistration;
if (closed) {
$sso.show();
} else {
$sso.hide();
}
};
refresh();
return $div;
};
Messages.admin_invitationCreate = "Create invitation link"; // XXX
Messages.admin_invitationHint = "Create invitation links to allow users to register even when registration is closed";
Messages.admin_invitationTitle = "Invitation links";
create['invitation'] = function () {
var key = 'invitation';
var $div = makeBlock(key); // Msg.admin_invitationHint, admin_invitationTitle
@ -1610,7 +1662,9 @@ Example
return $div;
};
Messages.admin_usersAdd = "Add known user";
Messages.admin_usersAdd = "Add known user"; // XXX
Messages.admin_userHint = "List of known users. You can add more using the form and select automated options";
Messages.admin_userTitle = "Known users";
create['users'] = function () {
var key = 'users';
var $div = makeBlock(key); // Msg.admin_usersHint, admin_usersTitle

View File

@ -34,6 +34,10 @@ define([
var hash = window.location.hash.slice(1);
token = hash;
$('body').removeClass('cp-register-closed');
} else if (Config.sso && Config.restrictRegistration && !Config.restrictSsoRegistration) {
$('body').find('.cp-register-det').css('display', 'flex');
$('body').find('#data').hide();
$('body').find('#userForm').hide();
}
// text and password input fields
@ -69,7 +73,6 @@ define([
var list = Config.sso.list.map(function (name) {
var b = h('button.btn.btn-secondary', name);
var $b = $(b).click(function () {
console.log('sso register click:', name);
$b.prop('disabled', 'disabled');
Login.ssoAuth(name, function (err, data) {
if (data.url) {

View File

@ -33,19 +33,22 @@ define([
secretKey: Nacl.util.decodeBase64(b64Keys.s),
publicKey: Nacl.util.decodeBase64(b64Keys.p)
};
var inviteToken = b64Keys.token;
ServerCommand(keys, {
command: 'SSO_AUTH_CB',
url: window.location.href
}, function (err, data) {
delete localStorage.CP_sso_auth;
document.cookie = 'ssotoken=; Max-Age=-99999999;';
if (data) { data.inviteToken = inviteToken; }
cb(err, data);
});
};
let ssoLoginRegister = function (seed, pw, jwt, name, isRegister) {
let ssoLoginRegister = function (seed, pw, jwt, name, isRegister, inviteToken) {
Login.loginOrRegister({
uname: seed,
passwd: pw,
token: inviteToken,
isRegister: isRegister,
onOTP: UI.getOTPScreen,
ssoAuth: {
@ -97,7 +100,7 @@ define([
hideTips: true,
});
setTimeout(function () { // Second timeout for the loading screen befofe Scrypt
ssoLoginRegister(seed, pw, jwt, name, data.register);
ssoLoginRegister(seed, pw, jwt, name, data.register, data.inviteToken);
}, 100);
}, 100);
@ -108,6 +111,10 @@ define([
return void next('');
}
if (data.register && ApiConfig.restrictSsoRegistration && !data.inviteToken) {
return void UI.errorLoadingScreen(Messages.register_registrationIsClosed);
}
// Registration and CP password disabled, continue
if (data.register && !ApiConfig.sso.password) {
return void next('');
@ -130,14 +137,17 @@ define([
$button.click(() => {
let pw = $pw.val();
let warning = Messages._getKey('register_passwordTooShort', [
Cred.MINIMUM_PASSWORD_LENGTH
]);
if (data.register && pw !== $pw2.val()) {
return void UI.warn(Messages.register_passwordsDontMatch);
}
if (data.register && pw && !Cred.isLongEnoughPassword(pw)) {
return void UI.warn(Messages.register_passwordTooShort);
return void UI.warn(warning);
}
if (data.register && !pw && ApiConfig.sso.password === 2) {
return void UI.warn(Messages.register_passwordTooShort);
return void UI.warn(warning);
}
$button.prop('disabled', 'disabled');