cryptpad/lib/challenge-commands/sso.js
2023-06-27 16:04:32 +02:00

230 lines
7.9 KiB
JavaScript

const Util = require("../common-util");
const Commands = module.exports;
const SSOUtils = require('../sso-utils');
const nThen = require("nthen");
const BlockStore = require("../storage/block");
const Block = require("../commands/block");
const TYPES = SSOUtils.TYPES;
const checkConfig = SSOUtils.checkConfig;
const getProviderConfig = SSOUtils.getProviderConfig;
const isValidConfig = SSOUtils.isValidConfig;
// Create an SSO authentication request
const auth = Commands.SSO_AUTH = function (Env, body, cb) {
if (!checkConfig(Env)) { return void cb('INVALID_SERVER_CONFIG'); }
const { provider } = body;
const cfg = getProviderConfig(Env, provider);
if (!cfg) { return void cb('UNRECOGNIZED_IDP'); }
if (!isValidConfig(cfg)) { return void cb('INVALID_IDP_CONFIG'); }
cb();
};
auth.complete = function (Env, body, cb, req, res) {
// If we're here it means a valid provider was given. We can start the authentication process
const { provider, register, publicKey } = body;
const cfg = getProviderConfig(Env, provider);
const idp = TYPES[cfg.type.toLowerCase()];
idp.auth(Env, cfg, (err, obj) => {
if (err) { return void cb(err); } // TODO log
const { url, token } = obj;
SSOUtils.writeRequest(Env, {
id: token,
type: idp.type,
provider: provider,
publicKey: publicKey,
register: Boolean(register)
}, (err) => {
if (err) { return void cb("E_REQ_WRITE"); }
let value = `ssotoken="${token}"; SameSite=Strict; HttpOnly`;
res.setHeader('Set-Cookie', value);
cb(void 0, {url: url});
});
});
};
// Receive authentication data from the IdP.
// Read the auth request, create a JWT and get a block seed for this SSO user.
const authCb = Commands.SSO_AUTH_CB = function (Env, body, cb, req) {
if (!checkConfig(Env)) { return void cb('INVALID_SERVER_CONFIG'); }
const { publicKey } = body;
const cookies = req.cookies;
const ssotoken = cookies.ssotoken;
if (!ssotoken) { return void cb('NO_COOKIE'); }
SSOUtils.readRequest(Env, ssotoken, (err, value) => {
if (err) { return void cb('ENOENT'); }
const data = Util.tryParse(value);
if (!data || !data.type || !TYPES[data.type]) { return void cb('INVALID_REQUEST_DATA'); }
if (publicKey !== data.publicKey) { return void cb('WRONG_SIGNATURE_KEYS'); }
cb();
});
};
authCb.complete = function (Env, body, cb, req) {
// If we're here it means a valid cookie was given. We can continue the authentication
const { url } = body;
const cookies = req.cookies;
const ssotoken = cookies.ssotoken;
if (!ssotoken) { return void cb('NO_COOKIE'); }
SSOUtils.readRequest(Env, ssotoken, (err, value) => {
SSOUtils.deleteRequest(Env, ssotoken);
if (err) { return void cb('ENOENT'); }
const data = Util.tryParse(value);
const cfg = getProviderConfig(Env, data.provider);
const idp = TYPES[data.type];
const register = data.register;
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) => {
if (err) { return void cb(err); }
cb(void 0, {
jwt: jwt,
name: name,
seed: user.seed,
password: Boolean(user.password),
register: isRegister
});
});
};
if (register) {
SSOUtils.writeUser(Env, id, (err, userData) => {
if (err && err.code === 'EEXIST') {
return void SSOUtils.readUser(Env, id, (err, userData) => {
if (err) { return void cb(err); }
next(userData, false);
});
}
if (err) { return void cb(err); }
next(userData, true);
});
} else {
SSOUtils.readUser(Env, id, (err, userData) => {
if (err) { return void cb(err); }
next(userData, false);
});
}
});
});
};
// XXX write block change-password? should be the same as otp but without otp code
const register = Commands.SSO_WRITE_BLOCK = function (Env, body, cb) {
const { publicKey, content } = body;
// they must provide a valid block public key
if (!Block.isValidBlockId(publicKey)) { return void cb("INVALID_KEY"); }
if (publicKey !== content.publicKey) { return void cb("INVALID_KEY"); }
const jwt = content.auth;
if (!jwt) { return void cb('NO_JWT'); }
BlockStore.isAvailable(Env, publicKey, (err, result) => {
if (err || result) {
return void cb(err || 'EEXISTS');
}
// No block at this location: continue
cb();
});
};
register.complete = function (Env, body, cb) {
const { publicKey, content } = body;
const jwt = content.auth;
const pw = content.hasPassword;
let payload;
let ssoUser;
// XXX UPDATE sso_user add password boolean
nThen((w) => {
SSOUtils.checkJWT(Env, jwt, w((err, _payload) => {
if (err) {
w.abort();
return void cb('INVALID_JWT_SIGNATURE');
}
payload = _payload;
}));
}).nThen((w) => {
const { sub } = payload;
SSOUtils.readUser(Env, sub, w((err, user) => {
if (err) {
w.abort();
console.log(err, sub);
return void cb('SSO_NO_USER');
}
ssoUser = user;
}));
}).nThen((w) => {
const { sub } = payload;
SSOUtils.writeBlock(Env, publicKey, sub, w((err) => {
if (err) {
w.abort();
return void cb('SSO_BLOCK_WRITE');
}
}));
}).nThen((w) => {
Block.writeLoginBlock(Env, content, w((err) => {
if (err) { w.abort(); }
}));
}).nThen((w) => {
if (!pw) { return; }
const { sub } = payload;
ssoUser.password = true;
SSOUtils.updateUser(Env, sub, ssoUser, w());
}).nThen(() => {
const { data, provider } = payload;
SSOUtils.makeSession(Env, publicKey, provider, data, cb);
});
};
const login = Commands.SSO_VALIDATE = function (Env, body, cb) {
const { publicKey, jwt } = body;
// they must provide a valid block public key
if (!Block.isValidBlockId(publicKey)) { return void cb("INVALID_KEY"); }
if (!jwt) { return void cb('NO_JWT'); }
BlockStore.isAvailable(Env, publicKey, (err, result) => {
if (err && !result) { return void cb(err); }
// Block found
cb();
});
};
login.complete = function (Env, body, cb) {
const { publicKey, jwt } = body;
let payload;
nThen((w) => {
SSOUtils.checkJWT(Env, jwt, w((err, _payload) => {
if (err) {
w.abort();
return void cb('INVALID_JWT_SIGNATURE');
}
payload = _payload;
}));
}).nThen((w) => {
const { sub } = payload;
SSOUtils.readUser(Env, sub, w((err) => {
if (err) {
w.abort();
console.log(err, sub);
return void cb('SSO_NO_USER');
}
}));
}).nThen((w) => {
SSOUtils.readBlock(Env, publicKey, w((err) => {
if (err) {
w.abort();
return void cb('SSO_BLOCK_WRITE');
}
}));
}).nThen(() => {
const { data, provider } = payload;
SSOUtils.makeSession(Env, publicKey, provider, data, cb);
});
};