mirror of https://github.com/xwiki-labs/cryptpad
SSO: prototype improvements
This commit is contained in:
parent
b93b5eae4e
commit
d6bf625733
|
@ -76,12 +76,13 @@ authCb.complete = function (Env, body, cb, req) {
|
|||
const cfg = getProviderConfig(Env, data.provider);
|
||||
const idp = TYPES[data.type];
|
||||
const register = data.register;
|
||||
const provider = data.provider;
|
||||
idp.authCb(Env, cfg, ssotoken, url, (err, obj) => {
|
||||
if (err) { return void cb(err); }
|
||||
const {id, idpData, name} = obj;
|
||||
|
||||
let next = (user, isRegister) => {
|
||||
SSOUtils.createJWT(Env, id, data.provider, idpData, (err, jwt) => {
|
||||
SSOUtils.createJWT(Env, id, provider, idpData, (err, jwt) => {
|
||||
if (err) { return void cb(err); }
|
||||
cb(void 0, {
|
||||
jwt: jwt,
|
||||
|
@ -94,19 +95,22 @@ authCb.complete = function (Env, body, cb, req) {
|
|||
};
|
||||
|
||||
if (register) {
|
||||
SSOUtils.writeUser(Env, id, (err, userData) => {
|
||||
SSOUtils.writeUser(Env, provider, id, (err, userData) => {
|
||||
if (err && err.code === 'EEXIST') {
|
||||
return void SSOUtils.readUser(Env, id, (err, userData) => {
|
||||
return void SSOUtils.readUser(Env, provider, id, (err, userData) => {
|
||||
if (err) { return void cb(err); }
|
||||
next(userData, false);
|
||||
next(userData, !userData.complete);
|
||||
});
|
||||
}
|
||||
if (err) { return void cb(err); }
|
||||
next(userData, true);
|
||||
});
|
||||
} else {
|
||||
SSOUtils.readUser(Env, id, (err, userData) => {
|
||||
SSOUtils.readUser(Env, provider, id, (err, userData) => {
|
||||
if (err) { return void cb(err); }
|
||||
if (!userData || !userData.complete) {
|
||||
return void cb('NO_USER');
|
||||
}
|
||||
next(userData, false);
|
||||
});
|
||||
}
|
||||
|
@ -148,8 +152,8 @@ register.complete = function (Env, body, cb) {
|
|||
payload = _payload;
|
||||
}));
|
||||
}).nThen((w) => {
|
||||
const { sub } = payload;
|
||||
SSOUtils.readUser(Env, sub, w((err, user) => {
|
||||
const { sub, provider } = payload;
|
||||
SSOUtils.readUser(Env, provider, sub, w((err, user) => {
|
||||
if (err) {
|
||||
w.abort();
|
||||
console.log(err, sub);
|
||||
|
@ -170,10 +174,10 @@ register.complete = function (Env, body, cb) {
|
|||
if (err) { w.abort(); }
|
||||
}));
|
||||
}).nThen((w) => {
|
||||
if (!pw) { return; }
|
||||
const { sub } = payload;
|
||||
ssoUser.password = true;
|
||||
SSOUtils.updateUser(Env, sub, ssoUser, w());
|
||||
const { sub, provider } = payload;
|
||||
ssoUser.password = Boolean(pw);
|
||||
ssoUser.complete = true;
|
||||
SSOUtils.updateUser(Env, provider, sub, ssoUser, w());
|
||||
}).nThen(() => {
|
||||
const { data, provider } = payload;
|
||||
SSOUtils.makeSession(Env, publicKey, provider, data, cb);
|
||||
|
@ -206,8 +210,8 @@ login.complete = function (Env, body, cb) {
|
|||
payload = _payload;
|
||||
}));
|
||||
}).nThen((w) => {
|
||||
const { sub } = payload;
|
||||
SSOUtils.readUser(Env, sub, w((err) => {
|
||||
const { sub, provider } = payload;
|
||||
SSOUtils.readUser(Env, provider, sub, w((err) => {
|
||||
if (err) {
|
||||
w.abort();
|
||||
console.log(err, sub);
|
||||
|
|
|
@ -377,7 +377,7 @@ app.use('/block/', function (req, res, next) {
|
|||
if (mfa_params && !content.mfa) { return void no(); }
|
||||
if (sso_params && !content.sso) { return void no(); }
|
||||
|
||||
if (content.mfa && content.mfa.exp && ((+new Date()) > content.mfa.exp)) {
|
||||
if (content.mfa && content.mfa.exp && (+new Date()) > content.mfa.exp) {
|
||||
Log.error("OTP_SESSION_EXPIRED", content.mfa);
|
||||
// XXX Only delete the mfa part
|
||||
Sessions.delete(Env, name, token, function (err) {
|
||||
|
@ -391,41 +391,19 @@ app.use('/block/', function (req, res, next) {
|
|||
}
|
||||
|
||||
|
||||
if (content.sso) {
|
||||
SSOUtils.checkSession(Env, content.sso, (err, state, newState) => {
|
||||
if (err || !state) {
|
||||
// XXX Only delete the mfa part
|
||||
Sessions.delete(Env, name, token, function (err) {
|
||||
if (err) {
|
||||
Log.error('SSO_SESSION_DELETE_EXPIRED_ERROR', err);
|
||||
return;
|
||||
}
|
||||
Log.info('SSO_SESSION_DELETE_EXPIRED', err);
|
||||
});
|
||||
return void no();
|
||||
if (content.sso && content.sso.exp && (+new Date()) > content.sso.exp) {
|
||||
Log.error("SSO_SESSION_EXPIRED", content.sso);
|
||||
Sessions.delete(Env, name, token, function (err) {
|
||||
if (err) {
|
||||
Log.error('SSO_SESSION_DELETE_EXPIRED_ERROR', err);
|
||||
return;
|
||||
}
|
||||
if (newState) {
|
||||
content.sso = newState;
|
||||
// XXX We should produce a new session ID here and send it back to the user
|
||||
Sessions.update(Env, name, token, JSON.stringify(content), (err) => {
|
||||
if (err) {
|
||||
Log.error('SSO_SESSION_UPDATE_ERROR', err);
|
||||
return;
|
||||
}
|
||||
Log.info('SSO_SESSION_UPDATED', err);
|
||||
});
|
||||
}
|
||||
next();
|
||||
Log.info('SSO_SESSION_DELETE_EXPIRED', err);
|
||||
});
|
||||
return;
|
||||
return void no();
|
||||
}
|
||||
|
||||
// we could also check whether the content of the file matches the token,
|
||||
// but clients don't have any influence over the reference and can only
|
||||
// request to create tokens that are scoped to a public key they control.
|
||||
// I don' think there's any practical benefit to such a check.
|
||||
|
||||
// So, interpret the existence of a file in that location as the continued
|
||||
// Interpret the existence of a file in that location as the continued
|
||||
// validity of the session. Fall through and let the built-in webserver
|
||||
// handle the 404 or serving the file.
|
||||
next();
|
||||
|
|
|
@ -4,7 +4,6 @@ const TYPE = 'oidc';
|
|||
|
||||
const getClient = (cfg, cb) => {
|
||||
OID.Issuer.discover(cfg.url).then((issuer) => { // XXX Only once for all users?
|
||||
console.log('Discovered issuer %s %O', issuer.issuer);
|
||||
const client = new issuer.Client({
|
||||
client_id: cfg.client_id,
|
||||
client_secret: cfg.client_secret,
|
||||
|
@ -55,30 +54,18 @@ module.exports = {
|
|||
expires_at: j.expires_at,
|
||||
access_token: j.access_token,
|
||||
refresh_token: j.refresh_token,
|
||||
id_token: j.id_token
|
||||
//id_token: j.id_token // XXX no need to store id_token?
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
checkSession: (Env, cfg, data, cb) => {
|
||||
const { refresh_token } = data;
|
||||
|
||||
/*
|
||||
getData: (Env, cfg, data, cb) => {
|
||||
// data = { refresh_token, access_token, expires_at }
|
||||
let t = new OID.TokenSet(data);
|
||||
if (!t.expired()) { return void cb(void 0, true); }
|
||||
getClient(cfg, (err, client) => {
|
||||
client.refresh(t.refresh_token).then((j) => {
|
||||
const newData = {
|
||||
refresh_token: refresh_token,
|
||||
id_token: j.id_token,
|
||||
access_token: j.access_token,
|
||||
expires_at: j.expires_at
|
||||
};
|
||||
cb(void 0, true, newData);
|
||||
}, () => {
|
||||
// Error: can't renew token
|
||||
cb(void 0, false);
|
||||
});
|
||||
});
|
||||
// TODO get userinfo using access_token
|
||||
// use refresh_token if access expired
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
|
|
@ -6,6 +6,8 @@ const Util = require("./common-util");
|
|||
|
||||
const SSOUtils = module.exports;
|
||||
|
||||
const SESSION_EXPIRATIION = 12 * 3600 * 1000; // XXX Hours? Days? Weeks? Configurable?
|
||||
|
||||
// XXX const SAML = require('node-saml'); // https://www.npmjs.com/package/node-saml
|
||||
const TYPES = SSOUtils.TYPES = {
|
||||
oidc: require('./plugins/sso/oidc')
|
||||
|
@ -29,12 +31,6 @@ SSOUtils.isValidConfig = (cfg) => {
|
|||
return idp.checkConfig(cfg);
|
||||
};
|
||||
|
||||
SSOUtils.checkSession = (Env, sessionData, cb) => {
|
||||
const cfg = getProviderConfig(Env, sessionData.provider);
|
||||
const idp = TYPES[cfg.type.toLowerCase()];
|
||||
idp.checkSession(Env, cfg, sessionData.data, cb);
|
||||
};
|
||||
|
||||
SSOUtils.deleteRequest = (Env, id) => {
|
||||
SSO.request.delete(Env, id, (err) => {
|
||||
if (!err) { return; }
|
||||
|
@ -60,9 +56,9 @@ SSOUtils.writeRequest = (Env, data, cb) => {
|
|||
};
|
||||
|
||||
|
||||
SSOUtils.writeUser = (Env, id, cb) => {
|
||||
SSOUtils.writeUser = (Env, provider, id, cb) => {
|
||||
const seed = Nacl.util.encodeBase64(Nacl.randomBytes(24));
|
||||
SSO.user.write(Env, id, JSON.stringify({
|
||||
SSO.user.write(Env, provider, id, JSON.stringify({
|
||||
seed: seed,
|
||||
password: false
|
||||
}), (err) => {
|
||||
|
@ -70,15 +66,15 @@ SSOUtils.writeUser = (Env, id, cb) => {
|
|||
cb(void 0, { seed });
|
||||
});
|
||||
};
|
||||
SSOUtils.readUser = (Env, id, cb) => {
|
||||
SSO.user.read(Env, id, (err, user) => {
|
||||
SSOUtils.readUser = (Env, provider, id, cb) => {
|
||||
SSO.user.read(Env, provider, id, (err, user) => {
|
||||
if (err) { return void cb(err); }
|
||||
cb(void 0, Util.tryParse(user));
|
||||
});
|
||||
};
|
||||
SSOUtils.updateUser = (Env, id, data, cb) => {
|
||||
SSO.user.delete(Env, id, () => {
|
||||
SSO.user.write(Env, id, JSON.stringify(data), (err) => {
|
||||
SSOUtils.updateUser = (Env, provider, id, data, cb) => {
|
||||
SSO.user.delete(Env, provider, id, () => {
|
||||
SSO.user.write(Env, provider, id, JSON.stringify(data), (err) => {
|
||||
if (err) { return void cb(err); }
|
||||
cb();
|
||||
});
|
||||
|
@ -146,6 +142,7 @@ SSOUtils.makeSession = (Env, publicKey, provider, ssoData, cb) => {
|
|||
// XXX If we already have an OTP session, recover it
|
||||
Sessions.write(Env, publicKey, sessionId, JSON.stringify({
|
||||
sso: {
|
||||
exp: +new Date() + SESSION_EXPIRATIION,
|
||||
provider: provider,
|
||||
data: ssoData
|
||||
}
|
||||
|
|
|
@ -42,14 +42,6 @@ Sessions.delete = function (Env, id, ref, cb) {
|
|||
Basic.delete(Env, path, cb);
|
||||
};
|
||||
|
||||
Sessions.update = function (Env, id, ref, data, cb) {
|
||||
var path = pathFromId(Env, id, ref);
|
||||
Basic.delete(Env, path, (err) => {
|
||||
if (err) { return void cb(err); }
|
||||
Basic.write(Env, path, data, cb);
|
||||
});
|
||||
};
|
||||
|
||||
Sessions.deleteUser = function (Env, id, cb) {
|
||||
if (!id || typeof(id) !== 'string') { return; }
|
||||
id = Util.escapeKeyCharacters(id);
|
||||
|
|
|
@ -21,13 +21,17 @@ var pathFromId = function (Env, id, subPath) {
|
|||
var reqPathFromId = function (Env, id) {
|
||||
return pathFromId(Env, id, 'sso_request');
|
||||
};
|
||||
var userPathFromId = function (Env, id) {
|
||||
return pathFromId(Env, id, 'sso_user');
|
||||
};
|
||||
var blockPathFromId = function (Env, id) {
|
||||
return pathFromId(Env, id, 'sso_block');
|
||||
};
|
||||
|
||||
var userPathFromId = function (Env, id, provider) {
|
||||
if (!id || typeof(id) !== 'string') { return; }
|
||||
if (!provider || typeof(provider) !== 'string') { return; }
|
||||
id = Util.escapeKeyCharacters(id);
|
||||
return Path.join(Env.paths.base, 'sso_user', provider, id.slice(0, 2), `${id}.json`);
|
||||
};
|
||||
|
||||
const Req = SSO.request = {};
|
||||
|
||||
Req.read = function (Env, id, cb) {
|
||||
|
@ -36,7 +40,6 @@ Req.read = function (Env, id, cb) {
|
|||
};
|
||||
Req.write = function (Env, id, data, cb) {
|
||||
var path = reqPathFromId(Env, id);
|
||||
console.log(path);
|
||||
Basic.write(Env, path, data, cb);
|
||||
};
|
||||
Req.delete = function (Env, id, cb) {
|
||||
|
@ -47,16 +50,16 @@ Req.delete = function (Env, id, cb) {
|
|||
|
||||
const User = SSO.user = {};
|
||||
|
||||
User.read = function (Env, id, cb) {
|
||||
var path = userPathFromId(Env, id);
|
||||
User.read = function (Env, provider, id, cb) {
|
||||
var path = userPathFromId(Env, id, provider);
|
||||
Basic.read(Env, path, cb);
|
||||
};
|
||||
User.write = function (Env, id, data, cb) {
|
||||
var path = userPathFromId(Env, id);
|
||||
User.write = function (Env, provider, id, data, cb) {
|
||||
var path = userPathFromId(Env, id, provider);
|
||||
Basic.write(Env, path, data, cb);
|
||||
};
|
||||
User.delete = function (Env, id, cb) {
|
||||
var path = userPathFromId(Env, id);
|
||||
User.delete = function (Env, provider, id, cb) {
|
||||
var path = userPathFromId(Env, id, provider);
|
||||
Basic.delete(Env, path, cb);
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue