SSO: OIDC auth

This commit is contained in:
yflory 2023-06-23 17:46:14 +02:00
parent 318202531f
commit 18d6ccdfd3
18 changed files with 557 additions and 126 deletions

1
.gitignore vendored
View File

@ -22,4 +22,5 @@ block/
logs/
privileged.conf
config/config.js
config/sso.js
*.sh

21
config/sso.example.js Normal file
View File

@ -0,0 +1,21 @@
module.exports = {
// Enable SSO login on this instance
enabled: false,
// Block registration for non-SSO users on this instance
enforced: false,
// Allow users to add an additional CryptPad password to their SSO account
cpPassword: false,
// List of SSO providers
list: [
/*
{
name: 'google',
type: 'oidc',
url: 'https://accounts.google.com',
client_id: "{your_client_id}",
client_secret: "{your_client_secret}"
}
*/
]
};

View File

@ -43,6 +43,39 @@ define([
setRedirectTo();
}
Exports.ssoRegister = function (provider, cb) {
var keys = Nacl.sign.keyPair();
localStorage.CP_sso_auth = JSON.stringify({
s: Nacl.util.encodeBase64(keys.secretKey),
p: Nacl.util.encodeBase64(keys.publicKey)
});
ServerCommand(keys, {
command: 'SSO_AUTH',
provider: provider,
register: true
}, cb);
};
Exports.ssoRegisterCb = function () {
var b64Keys = Util.tryParse(localStorage.CP_sso_auth);
if (!b64Keys) {
throw new Error("MISSING_SIGNATURE_KEYS");
}
var keys = {
secretKey: Nacl.util.decodeBase64(b64Keys.s),
publicKey: Nacl.util.decodeBase64(b64Keys.p)
};
ServerCommand(keys, {
command: 'SSO_AUTH_CB',
url: window.location.href
}, function (err, data) {
delete localStorage.CP_sso_auth;
if (data && data.state) { window.location.href = '/register/'; }
});
};
Exports.ssoLogin = function () {
};
var allocateBytes = Exports.allocateBytes = function (bytes) {
var dispense = Cred.dispenser(bytes);

View File

@ -38,6 +38,8 @@ define([
undefined:
h('button#register.cp-secondary', Msg.login_register)
),
h('button.login', Msg.login_login),
h('br'),
h('button.login', Msg.login_login)
])
]),

View File

@ -79,7 +79,8 @@ define([
UI.createCheckbox('import-recent', Msg.register_importRecent, true)
]),
termsCheck,
h('button#register', Msg.login_register)
h('button#register', Msg.login_register),
Config.sso ? h('div.cp-register-sso') : undefined
])
]),
])

View File

@ -0,0 +1,105 @@
const Util = require("../common-util");
const Commands = module.exports;
const SSOUtils = require('../sso-utils');
const checkConfig = (Env) => {
return Env && Env.sso && Env.sso.enabled && Array.isArray(Env.sso.list) && Env.sso.list.length;
};
const getProviderConfig = (Env, provider) => {
if (!checkConfig) { return; }
if (!provider) { return; }
const data = Env.sso.list.find((cfg) => { return cfg.name === provider; });
return data;
};
const TYPES = SSOUtils.TYPES;
const isValidConfig = (cfg) => {
if (!cfg) { return; }
if (!cfg.type) { return; }
const type = cfg.type.toLowerCase();
const idp = TYPES[type];
if (!idp) { return; }
return idp.checkConfig(cfg);
};
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});
});
});
};
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'); }
console.log('stored', data.publicKey);
console.log('used', publicKey);
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];
idp.authCb(Env, cfg, ssotoken, url, (err, obj) => {
if (err) { return void cb(err); }
const {id, idpData} = obj;
console.log(id, idpData);
SSOUtils.makeUser(Env, id, (err, userData) => {
if (err) { return void cb(err); }
// TODO
// makeTempSession()
cb(void 0, { state: true });
});
});
});
};

View File

@ -223,6 +223,8 @@ module.exports.create = function (config) {
evictionReport: {},
commandTimers: {},
sso: config.sso,
// initialized as undefined
bearerSecret: void 0,
curvePrivate: curve.secretKey,

View File

@ -73,6 +73,9 @@ COMMANDS.TOTP_REVOKE = TOTP.TOTP_REVOKE;
COMMANDS.TOTP_WRITE_BLOCK = TOTP.TOTP_WRITE_BLOCK;
COMMANDS.TOTP_REMOVE_BLOCK = TOTP.TOTP_REMOVE_BLOCK;
const SSO = require("./challenge-commands/sso.js");
COMMANDS.SSO_AUTH = SSO.SSO_AUTH;
COMMANDS.SSO_AUTH_CB = SSO.SSO_AUTH_CB;
var randomToken = () => Nacl.util.encodeBase64(Nacl.randomBytes(24)).replace(/\//g, '-');
@ -141,7 +144,7 @@ var handleCommand = function (Env, req, res) {
date: date,
});
});
});
}, req);
} catch (err) {
Env.Log.error("CHALLENGE_COMMAND_THROWN_ERROR", {
error: Util.serializeError(err),
@ -290,7 +293,7 @@ var handleResponse = function (Env, req, res) {
});
}
res.status(200).json(content);
});
}, req, res);
});
};

View File

@ -10,6 +10,7 @@ const AuthCommands = require("./http-commands");
const JWT = require("jsonwebtoken");
const MFA = require("./storage/mfa");
const Sessions = require("./storage/sessions");
const cookieParser = require("cookie-parser");
const DEFAULT_QUERY_TIMEOUT = 5000;
const PID = process.pid;
@ -158,6 +159,7 @@ var setHeaders = function (req, res) {
const Express = require("express");
var app = Express();
app.use(cookieParser());
(function () {
if (!Env.logFeedback) { return; }
@ -187,7 +189,6 @@ const wsProxy = createProxyMiddleware({
app.use('/cryptpad_websocket', wsProxy);
app.use('/blob', function (req, res, next) {
/* Head requests are used to check the size of a blob.
Clients can configure a maximum size to download automatically,
@ -484,6 +485,13 @@ var makeRouteCache = function (template, cacheName) {
};
};
const ssoList = Env.sso && Env.sso.enabled && Array.isArray(Env.sso.list) &&
Env.sso.list.map(function (obj) { return obj.name; });
const ssoCfg = ssoList.length ? {
force: (Env.sso && Env.sso.enforced && 1) || 0,
password: (Env.sso && Env.sso.cpPassword && 1) || 0,
list: ssoList
} : false;
var serveConfig = makeRouteCache(function () {
return [
'define(function(){',
@ -510,6 +518,7 @@ var serveConfig = makeRouteCache(function () {
shouldUpdateNode: Env.shouldUpdateNode || undefined,
listMyInstance: Env.listMyInstance,
accounts_api: Env.accounts_api,
sso: ssoCfg
}, null, '\t'),
'});'
].join(';\n');

View File

@ -42,5 +42,12 @@ if (!isPositiveNumber(config.premiumUploadSize) || config.premiumUploadSize < co
delete config.premiumUploadSize;
}
config.sso = {};
try {
config.sso = require("../config/sso");
} catch (e) {
console.log("SSO config not found");
}
module.exports = config;

67
lib/plugins/sso/oidc.js Normal file
View File

@ -0,0 +1,67 @@
const OID = require('openid-client');
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,
redirect_uris: ['http://localhost:3000/ssoauth'], // XXX Use httpUnsafeOrigin or...
response_types: ['code'],
});
cb(void 0, client);
}, (err) => {
cb(err);
});
};
module.exports = {
type: TYPE,
checkConfig: (cfg) => {
return cfg.url && cfg.client_id && cfg.client_secret;
},
auth: (Env, cfg, cb) => {
getClient(cfg, (err, client) => {
if (err) { return void cb ('E_OIDC_CONNECT'); }
const generators = OID.generators;
const code_verifier = generators.codeVerifier();
// store the code_verifier in your framework's session mechanism, if it is a cookie based solution
// it should be httpOnly (not readable by javascript) and encrypted.
console.log(code_verifier);
const code_challenge = generators.codeChallenge(code_verifier);
const url = client.authorizationUrl({
scope: 'openid email profile',// https://www.googleapis.com/auth/contacts.readonly', // https://www.google.com/m8/feeds
resource: 'http://localhost:3000/ssoauth/',
access_type: 'offline',
code_challenge,
code_challenge_method: 'S256',
});
cb(void 0, { url: url, token: code_verifier });
});
},
authCb: (Env, cfg, token, url, cb) => {
getClient(cfg, (err, client) => {
if (err) { return void cb ('E_OIDC_CONNECT'); }
const params = client.callbackParams(url);
client.callback('http://localhost:3000/ssoauth', params, { code_verifier: token })
.then((tokenSet) => {
let j = tokenSet;
let c = tokenSet.claims();
console.log(j, c);
cb(void 0, {
id: c.sub,
idpData: {
access_token: j.access_token,
refresh_token: j.refresh_token,
id_token: j.id_token
}
});
});
});
}
};

71
lib/sso-utils.js Normal file
View File

@ -0,0 +1,71 @@
const SSO = require("./storage/sso");
const Nacl = require("tweetnacl/nacl-fast");
const SSOUtils = module.exports;
// XXX const SAML = require('node-saml'); // https://www.npmjs.com/package/node-saml
SSOUtils.TYPES = {
oidc: require('./plugins/sso/oidc')
};
SSOUtils.deleteRequest = (Env, id) => {
SSO.request.delete(Env, id, (err) => {
if (!err) { return; }
console.log(`Failed to delete SSO request ${id}`);
// XXX log?
});
};
SSOUtils.readRequest = (Env, id, cb) => {
SSO.request.read(Env, id, cb);
};
SSOUtils.writeRequest = (Env, data, cb) => {
if (!data || !data.id || !data.type) { return void cb ('INVALID_REQUEST'); }
const id = data.id;
const value = {
type: data.type,
register: data.register,
provider: data.provider,
publicKey: data.publicKey,
time: +new Date()
};
SSO.request.write(Env, id, JSON.stringify(value), cb);
};
SSOUtils.makeUser = (Env, id, cb) => {
const seed = Nacl.util.encodeBase64(Nacl.util.randomBytes(24));
SSO.User.write(Env, id, JSON.stringify({
seed: seed,
password: false // XXX maybe we don't need that flag, we can check if the block exists later
}), (err) => {
if (err) { return void cb(err); }
cb(void 0, { seed });
});
};
SSOUtils.makeSession = (Env, publicKey, provider, ssoData, cb) => {
const sessionId = Sessions.randomId();
// XXX If we already have an OTP session, recover it
Sessions.write(Env, publicKey, sessionId, JSON.stringify({
sso: {
provider: provider,
data: ssoData
}
}), function (err) {
if (err) {
Env.Log.error("SSO_SESSION_WRITE", {
error: Util.serializeError(err),
publicKey: publicKey,
sessionId: sessionId,
});
return void cb("SESSION_WRITE_ERROR");
}
cb(void 0, {
bearer: sessionId,
});
});
};

57
lib/storage/sso.js Normal file
View File

@ -0,0 +1,57 @@
const Basic = require("./basic");
const Path = require("node:path");
const Util = require("../common-util");
const SSO = module.exports;
/* This module manages storage related to Single Sign-On (SSO) settings.
A first part (sso-requests) contains temporary files for sso authentication with a remote service
and a second part (sso users) is a database of accounts registered via SSO.
The path for requests is based on the "authentication request" token depending on the type of SSO.
The path for the user database is based on their persistent identifier (id) from the SSO.
*/
var pathFromId = function (Env, id, subPath) {
if (!id || typeof(id) !== 'string') { return; }
id = Util.escapeKeyCharacters(id);
return Path.join(Env.paths.base, subPath, id.slice(0, 2), `${id}.json`);
};
var reqPathFromId = function (Env, id) {
return pathFromId(Env, id, 'sso_request');
};
var userPathFromId = function (Env, id) {
return pathFromId(Env, id, 'sso');
};
const Req = SSO.request = {};
Req.read = function (Env, id, cb) {
var path = reqPathFromId(Env, id);
Basic.read(Env, path, 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) {
var path = reqPathFromId(Env, id);
Basic.delete(Env, path, cb);
};
const User = SSO.user = {};
User.read = function (Env, id, cb) {
var path = userPathFromId(Env, id);
Basic.read(Env, path, cb);
};
User.write = function (Env, id, data, cb) {
var path = userPathFromId(Env, id);
Basic.write(Env, path, data, cb);
};
User.delete = function (Env, id, cb) {
var path = userPathFromId(Env, id);
Basic.delete(Env, path, cb);
};

250
package-lock.json generated
View File

@ -12,6 +12,7 @@
"@mcrowe/minibloom": "^0.2.0",
"chainpad-crypto": "^0.2.5",
"chainpad-server": "^5.1.0",
"cookie-parser": "^1.4.6",
"express": "~4.18.2",
"fs-extra": "^7.0.0",
"get-folder-size": "^2.0.1",
@ -20,6 +21,7 @@
"netflux-websocket": "^0.1.20",
"notp": "^2.0.3",
"nthen": "0.1.8",
"openid-client": "^5.4.2",
"prompt-confirm": "^2.0.4",
"pull-stream": "^3.6.1",
"saferphore": "0.0.1",
@ -1011,6 +1013,26 @@
"node": ">= 0.6"
}
},
"node_modules/cookie-parser": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
"dependencies": {
"cookie": "0.4.1",
"cookie-signature": "1.0.6"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/cookie-parser/node_modules/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@ -1749,25 +1771,15 @@
}
},
"node_modules/glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
"integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": {
"is-glob": "^3.1.0",
"path-dirname": "^1.0.0"
}
},
"node_modules/glob-parent/node_modules/is-glob": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
"integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
"dev": true,
"dependencies": {
"is-extglob": "^2.1.0"
"is-glob": "^4.0.1"
},
"engines": {
"node": ">=0.10.0"
"node": ">= 6"
}
},
"node_modules/glob-to-regexp": {
@ -2220,6 +2232,14 @@
"node": ">=0.10.0"
}
},
"node_modules/jose": {
"version": "4.14.4",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz",
"integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
@ -2727,6 +2747,14 @@
"node": ">=0.10.0"
}
},
"node_modules/object-hash": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
"engines": {
"node": ">= 6"
}
},
"node_modules/object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
@ -2758,6 +2786,14 @@
"node": ">=0.10.0"
}
},
"node_modules/oidc-token-hash": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
"integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==",
"engines": {
"node": "^10.13.0 || >=12.0.0"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@ -2778,6 +2814,20 @@
"wrappy": "1"
}
},
"node_modules/openid-client": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.4.2.tgz",
"integrity": "sha512-lIhsdPvJ2RneBm3nGBBhQchpe3Uka//xf7WPHTIglery8gnckvW7Bd9IaQzekzXJvWthCMyi/xVEyGW0RFPytw==",
"dependencies": {
"jose": "^4.14.1",
"lru-cache": "^6.0.0",
"object-hash": "^2.2.0",
"oidc-token-hash": "^5.0.3"
},
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
@ -2808,12 +2858,6 @@
"node": ">=0.10.0"
}
},
"node_modules/path-dirname": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
"integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
"dev": true
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@ -3039,17 +3083,6 @@
"node": ">=0.10.0"
}
},
"node_modules/prompt-choices/node_modules/set-value": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-3.0.3.tgz",
"integrity": "sha512-Xsn/XSatoVOGBbp5hs3UylFDs5Bi9i+ArpVJKdHPniZHoEgRniXTqHWrWrGQ0PbEClVT6WtfnBwR8CAHC9sveg==",
"dependencies": {
"is-plain-object": "^2.0.4"
},
"engines": {
"node": ">=6.0"
}
},
"node_modules/prompt-choices/node_modules/shallow-clone": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
@ -3391,39 +3424,19 @@
}
},
"node_modules/set-value": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
"integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
"dev": true,
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-4.0.1.tgz",
"integrity": "sha512-ayATicCYPVnlNpFmjq2/VmVwhoCQA9+13j8qWp044fmFE3IFphosPtRM+0CJ5xoIx5Uy52fCcwg3XeH2pHbbPQ==",
"funding": [
"https://github.com/sponsors/jonschlinkert",
"https://paypal.me/jonathanschlinkert",
"https://jonschlinkert.dev/sponsor"
],
"dependencies": {
"extend-shallow": "^2.0.1",
"is-extendable": "^0.1.1",
"is-plain-object": "^2.0.3",
"split-string": "^3.0.1"
"is-plain-object": "^2.0.4"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/set-value/node_modules/extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"dependencies": {
"is-extendable": "^0.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/set-value/node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
"dev": true,
"engines": {
"node": ">=0.10.0"
"node": ">=11.0"
}
},
"node_modules/setprototypeof": {
@ -4738,7 +4751,7 @@
"get-value": "^2.0.6",
"has-value": "^1.0.0",
"isobject": "^3.0.1",
"set-value": "^2.0.0",
"set-value": "4.0.1",
"to-object-path": "^0.3.0",
"union-value": "^1.0.0",
"unset-value": "^1.0.0"
@ -4980,6 +4993,22 @@
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
},
"cookie-parser": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
"requires": {
"cookie": "0.4.1",
"cookie-signature": "1.0.6"
},
"dependencies": {
"cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
}
}
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@ -5387,7 +5416,7 @@
"requires": {
"@mrmlnc/readdir-enhanced": "^2.2.1",
"@nodelib/fs.stat": "^1.1.2",
"glob-parent": "^3.1.0",
"glob-parent": "5.1.2",
"is-glob": "^4.0.0",
"merge2": "^1.2.3",
"micromatch": "^3.1.10"
@ -5556,24 +5585,12 @@
}
},
"glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
"integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^3.1.0",
"path-dirname": "^1.0.0"
},
"dependencies": {
"is-glob": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
"integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
"dev": true,
"requires": {
"is-extglob": "^2.1.0"
}
}
"is-glob": "^4.0.1"
}
},
"glob-to-regexp": {
@ -5907,6 +5924,11 @@
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
},
"jose": {
"version": "4.14.4",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz",
"integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g=="
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
@ -6309,6 +6331,11 @@
}
}
},
"object-hash": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="
},
"object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
@ -6331,6 +6358,11 @@
"isobject": "^3.0.1"
}
},
"oidc-token-hash": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
"integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw=="
},
"on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@ -6348,6 +6380,17 @@
"wrappy": "1"
}
},
"openid-client": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.4.2.tgz",
"integrity": "sha512-lIhsdPvJ2RneBm3nGBBhQchpe3Uka//xf7WPHTIglery8gnckvW7Bd9IaQzekzXJvWthCMyi/xVEyGW0RFPytw==",
"requires": {
"jose": "^4.14.1",
"lru-cache": "^6.0.0",
"object-hash": "^2.2.0",
"oidc-token-hash": "^5.0.3"
}
},
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
@ -6369,12 +6412,6 @@
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
"dev": true
},
"path-dirname": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
"integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
"dev": true
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@ -6528,7 +6565,7 @@
"log-utils": "^0.2.1",
"pointer-symbol": "^1.0.0",
"radio-symbol": "^2.0.0",
"set-value": "^3.0.0",
"set-value": "4.0.1",
"strip-color": "^0.1.0",
"terminal-paginator": "^2.0.2",
"toggle-array": "^1.0.1"
@ -6549,14 +6586,6 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-6.0.0.tgz",
"integrity": "sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6/538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg=="
},
"set-value": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-3.0.3.tgz",
"integrity": "sha512-Xsn/XSatoVOGBbp5hs3UylFDs5Bi9i+ArpVJKdHPniZHoEgRniXTqHWrWrGQ0PbEClVT6WtfnBwR8CAHC9sveg==",
"requires": {
"is-plain-object": "^2.0.4"
}
},
"shallow-clone": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
@ -6836,32 +6865,11 @@
}
},
"set-value": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
"integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
"dev": true,
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-4.0.1.tgz",
"integrity": "sha512-ayATicCYPVnlNpFmjq2/VmVwhoCQA9+13j8qWp044fmFE3IFphosPtRM+0CJ5xoIx5Uy52fCcwg3XeH2pHbbPQ==",
"requires": {
"extend-shallow": "^2.0.1",
"is-extendable": "^0.1.1",
"is-plain-object": "^2.0.3",
"split-string": "^3.0.1"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
},
"is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
"dev": true
}
"is-plain-object": "^2.0.4"
}
},
"setprototypeof": {
@ -7351,7 +7359,7 @@
"arr-union": "^3.1.0",
"get-value": "^2.0.6",
"is-extendable": "^0.1.1",
"set-value": "^2.0.1"
"set-value": "4.0.1"
},
"dependencies": {
"is-extendable": {

View File

@ -15,6 +15,7 @@
"@mcrowe/minibloom": "^0.2.0",
"chainpad-crypto": "^0.2.5",
"chainpad-server": "^5.1.0",
"cookie-parser": "^1.4.6",
"express": "~4.18.2",
"fs-extra": "^7.0.0",
"get-folder-size": "^2.0.1",
@ -23,6 +24,7 @@
"netflux-websocket": "^0.1.20",
"notp": "^2.0.3",
"nthen": "0.1.8",
"openid-client": "^5.4.2",
"prompt-confirm": "^2.0.4",
"pull-stream": "^3.6.1",
"saferphore": "0.0.1",

View File

@ -1,4 +1,5 @@
define([
'/api/config',
'jquery',
'/customize/login.js',
'/common/cryptpad-common.js',
@ -14,7 +15,7 @@ define([
'/customize/pages.js',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
], function ($, Login, Cryptpad, /*Test,*/ Cred, UI, Util, Realtime, Constants, Feedback, LocalStore, h, Pages) {
], function (Config, $, Login, Cryptpad, /*Test,*/ Cred, UI, Util, Realtime, Constants, Feedback, LocalStore, h, Pages) {
if (window.top !== window) { return; }
var Messages = Cryptpad.Messages;
$(function () {
@ -50,6 +51,30 @@ define([
var br = function () { return h('br'); };
Messages.register_nameTooLong = "Usernames must be shorter than {0} characters"; // XXX
if (Config.sso) {
// TODO
// Config.sso.force => no legacy login allowed
// Config.sso.password => cp password required or forbidden
// Config.sso.list => list of configured identity providers
var $sso = $('div.cp-register-sso');
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.ssoRegister(name, function (err, data) {
console.error(err, data);
if (data.url) {
window.location.href = data.url;
}
});
// XXX Server command
});
return b;
});
$sso.append(list);
}
var registerClick = function () {
var uname = $uname.val().trim();
// trim whitespace surrounding the username since it is otherwise included in key derivation

14
www/ssoauth/index.html Normal file
View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<!-- If this file is not called customize.dist/src/template.html, it is generated -->
<head>
<title data-localization="main_title">CryptPad: Collaboration suite, encrypted and open-source</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" type="image/png" href="/customize/favicon/main-favicon.png" id="favicon"/>
<script async data-bootload="/ssoauth/main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
</head>
<body class="html">
<noscript></noscript>

3
www/ssoauth/main.js Normal file
View File

@ -0,0 +1,3 @@
define(['/customize/login.js'], function (Login) {
Login.ssoRegisterCb();
});