diff --git a/customize.dist/login.js b/customize.dist/login.js index 8a27ee4ff..efce968ca 100644 --- a/customize.dist/login.js +++ b/customize.dist/login.js @@ -221,21 +221,21 @@ define([ // If they are trying to register, // and the proxy is empty, then there is no 'legacy user' either // so we should just shut down this session and disconnect. - rt.network.disconnect(); + //rt.network.disconnect(); return; // proceed to the next async block } // they tried to just log in but there's no such user // and since we're here at all there is no modern-block if (!isRegister && isProxyEmpty(rt.proxy)) { - rt.network.disconnect(); // clean up after yourself + //rt.network.disconnect(); // clean up after yourself waitFor.abort(); return void cb('NO_SUCH_USER', res); } // they tried to register, but those exact credentials exist if (isRegister && !isProxyEmpty(rt.proxy)) { - rt.network.disconnect(); + //rt.network.disconnect(); waitFor.abort(); Feedback.send('LOGIN', true); return void cb('ALREADY_REGISTERED', res); @@ -247,6 +247,7 @@ define([ // so setting them is just a precaution to keep things in good shape res.proxy = rt.proxy; res.realtime = rt.realtime; + res.network = rt.network; // they're registering... res.userHash = opt.userHash; @@ -317,6 +318,7 @@ define([ res.proxy = rt.proxy; res.realtime = rt.realtime; + res.network = rt.network; // they're registering... res.userHash = userHash; @@ -328,14 +330,14 @@ define([ // this really shouldn't happen, but let's handle it anyway Feedback.send('EMPTY_LOGIN_WITH_BLOCK'); - rt.network.disconnect(); // clean up after yourself + //rt.network.disconnect(); // clean up after yourself waitFor.abort(); return void cb('NO_SUCH_USER', res); } // they tried to register, but those exact credentials exist if (isRegister && !isProxyEmpty(rt.proxy)) { - rt.network.disconnect(); + //rt.network.disconnect(); waitFor.abort(); res.blockHash = blockHash; if (shouldImport) { @@ -462,7 +464,8 @@ define([ var proceed = function (result) { hashing = false; - if (test && typeof test === "function" && test()) { return; } + // NOTE: test is also use as a cb for the install page + if (test && typeof test === "function" && test(result)) { return; } LocalStore.clearLoginToken(); Realtime.whenRealtimeSyncs(result.realtime, function () { Exports.redirect(); diff --git a/customize.dist/pages/install.js b/customize.dist/pages/install.js new file mode 100644 index 000000000..7bbf9e4cd --- /dev/null +++ b/customize.dist/pages/install.js @@ -0,0 +1,87 @@ +define([ + '/api/config', + 'jquery', + '/common/hyperscript.js', + '/common/common-interface.js', + '/customize/messages.js', + '/customize/pages.js' +], function (Config, $, h, UI, Msg, Pages) { + Config.adminKeys = []; + return function () { + // Redirect to drive if this instance already has admins + if (Array.isArray(Config.adminKeys) && Config.adminKeys.length) { + document.location.href = '/drive/'; + return; + } + +/* +Msg.install_header = "CryptPad Install"; // XXX +Msg.install_notes = ""; // XXX +*/ +Msg.install_token = "Install token"; + + document.title = Msg.install_header; + + var frame = function (content) { + return [ + h('div#cp-main', [ + //Pages.infopageTopbar(), + h('div.container.cp-container', [ + //h('div.row.cp-page-title', h('h1', Msg.install_header)), + h('div.row.cp-page-title', h('h1', Msg.register_header)), + ].concat(content)), + Pages.infopageFooter(), + ]), + ]; + }; + + return frame([ + h('div.row.cp-register-det', [ + h('div#data.hidden.col-md-6', [ + h('h2', Msg.register_notes_title), + //Pages.setHTML(h('div.cp-register-notes'), Msg.install_notes) + Pages.setHTML(h('div.cp-register-notes'), Msg.register_notes) + ]), + h('div.cp-reg-form.col-md-6', [ + h('div#userForm.form-group.hidden', [ + h('div.cp-register-instance', [ + Msg._getKey('register_instance', [ Pages.Instance.name ]), + /*h('br'), + h('a', { + href: '/features.html' + }, Msg.register_whyRegister)*/ + ]), + h('input.form-control#installtoken', { + type: 'text', + placeholder: Msg.install_token + }), + h('input.form-control#username', { + type: 'text', + autocomplete: 'off', + autocorrect: 'off', + autocapitalize: 'off', + spellcheck: false, + placeholder: Msg.login_username, + autofocus: true, + }), + h('input.form-control#password', { + type: 'password', + placeholder: Msg.login_password, + }), + h('input.form-control#password-confirm', { + type: 'password', + placeholder: Msg.login_confirm, + }), + /*h('div.checkbox-container', [ + UI.createCheckbox('import-recent', Msg.register_importRecent, true) + ]),*/ + h('button#register', Msg.login_register) + ]) + ]), + ]) + ]); + }; + +}); + diff --git a/customize.dist/src/less2/pages/page-install.less b/customize.dist/src/less2/pages/page-install.less new file mode 100644 index 000000000..696c3e4c1 --- /dev/null +++ b/customize.dist/src/less2/pages/page-install.less @@ -0,0 +1,126 @@ +@import (reference) "../include/infopages.less"; +@import (reference) "../include/colortheme-all.less"; +@import (reference) "../include/alertify.less"; +@import (reference) "../include/checkmark.less"; +@import (reference) "../include/forms.less"; + +&.cp-page-install { + .infopages_main(); + .forms_main(); + + .alertify_main(); + .checkmark_main(20px); + + .cp-container { + .form-group { + .cp-register-instance { + text-align: center; + margin-bottom: 10px; + } + #register { + &.btn { + padding: .5rem .5rem; + } + margin-top: 16px; + font-size: 1.25em; + min-width: 30%; + } + } + padding-bottom: 3em; + min-height: 5vh; + } + .alertify { + // workaround for alertify making empty p + p:empty { + display: none; + } + + nav { + display: flex; + align-items: center; + justify-content: flex-end; + } + + @media screen and (max-width: 600px) { + nav .btn-danger { + line-height: inherit; + } + } + + } + .cp-restricted-registration { + text-align: center !important; + } + + .cp-register-det { + #data { + + p { + li { + margin-bottom: 1em; + } + .fa { + padding-right: 10px; + } + } + h3 { + font-weight: 700; + margin-bottom: 1em; + } + } + .cp-reg-form { + img { + margin-top: 0px; + position: relative; + z-index: 0; + } + } + #userForm { + padding: 15px; + background-color: @cp_static-card-bg; + position: relative; + z-index: 2; + margin-bottom: 100px; + border-radius: @infopages-radius-L; + .cp-shadow(); + .form-control { + border-radius: @infopages-radius; + color: @cryptpad_text_col; + background-color: @cp_forms-bg; + margin-bottom: 10px; + &:focus { + border-color: @cryptpad_color_brand; + } + .tools_placeholder-color(); + } + .checkbox-container { + color: @cryptpad_text_col; + } + button#register { + margin-top: 10px; + } + } + + .cp-register-notes { + ul.cp-notes-list { + list-style: none; + margin-left: 0; + padding-left: 30px; + position: relative; + li { + margin-bottom: 10px; + &::before { + position: absolute; + left: 0; + font-family: "FontAwesome"; + content: "\f071"; + } + .red { + background-color: @cp_static-danger; + } + } + } + } + } +} + diff --git a/customize.dist/template.js b/customize.dist/template.js index fae7ca2bd..332471630 100644 --- a/customize.dist/template.js +++ b/customize.dist/template.js @@ -53,6 +53,8 @@ $(function () { if (/^\/register\//.test(pathname)) { require([ '/register/main.js' ], function () {}); + } else if (/^\/install\//.test(pathname)) { + require([ '/install/main.js' ], function () {}); } else if (/^\/login\//.test(pathname)) { require([ '/login/main.js' ], function () {}); } else if (/^\/($|^\/index\.html$)/.test(pathname)) { diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index 365987229..a05b177a2 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -777,6 +777,25 @@ var commands = { REMOVE_DOCUMENT: removeDocument, }; +// addFirstAdmin is an anon_rpc command +Admin.addFirstAdmin = function (Env, data, cb) { + var token = data.token; + if (token !== Env.installToken) { return void cb('FORBIDDEN'); } + if (Array.isArray(Env.admins) && Env.admins.length) { return void cb('EEXISTS'); } + + var key = data.edPublic; + if (token.length !== 64 || data.edPublic.length !== 44) { return void cb('INVALID_ARGS'); } + + adminDecree(Env, null, function (err) { + if (err) { return void cb(err); } + Env.flushCache(); + cb(); + }, ['ADD_FIRST_ADMIN', [ + 'ADD_ADMIN_KEY', + [key] + ]], ""); +}; + Admin.command = function (Env, safeKey, data, _cb, Server) { var cb = Util.once(Util.mkAsync(_cb)); diff --git a/lib/decrees.js b/lib/decrees.js index 1d48f0164..7afac7578 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -322,6 +322,34 @@ commands.RM_QUOTA = function (Env, args) { return true; }; +commands.ADD_INSTALL_TOKEN = function (Env, args) { + if (!Array.isArray(args) || args.length !== 1 || !args[0]) { + throw new Error("INVALID_ARGS"); + } + + var token = args[0]; + + // XXX check length, etc. ? + + Env.installToken = token; + + return true; +}; +commands.ADD_ADMIN_KEY = function (Env, args) { + if (!Array.isArray(args) || args.length !== 1 || !args[0]) { + throw new Error("INVALID_ARGS"); + } + + Env.admins = Env.admins || []; + + + var key = Keys.canonicalize(args[0]); + if (!key) { throw new Error("INVALID_KEY"); } + Env.admins.push(key); + + return true; +}; + // [, , ,