Genesis commit
35
config/default.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"bip32": {
|
||||
"serverExtendedPrivateKey": "xprivgeneratebyyourservicespecificallytorunonthisserver",
|
||||
"serverExtendedPrivateKeyDerivationPath": "a/123/456/789", // Doesn't support hardned keys?
|
||||
"serviceAuthenticatingExtendedPublicKey": "xpubfortheservicewhichownsthisandwhichclientscanusetoauthenticateserviceownedresources"
|
||||
},
|
||||
"bcrypt" : {
|
||||
"salt" : 9
|
||||
},
|
||||
"database" : {
|
||||
"dialect" : "sqlite",
|
||||
"host" : "localhost",
|
||||
"database" : "databases",
|
||||
"username" : "databases",
|
||||
"password" : "sdaabasfd",
|
||||
"storage" : "database.sqlite",
|
||||
"define": {
|
||||
"paranoid": true
|
||||
},
|
||||
"logging": false
|
||||
// Encryption on database...
|
||||
// "dialectOptions": {
|
||||
// "encrypt": true
|
||||
// }
|
||||
},
|
||||
"server" : {
|
||||
"secret" : "don't forget to keep your secrets secret",
|
||||
"port" : 8765,
|
||||
"cookie": {
|
||||
// "domain": ".sigidli.com"
|
||||
},
|
||||
"compiled-render": false,
|
||||
"domain": "example.org"
|
||||
}
|
||||
}
|
16
index.js
Normal file
@ -0,0 +1,16 @@
|
||||
var config = require("config");
|
||||
|
||||
var server = require('./server/server.js')();
|
||||
var persistence = require('./persistence/persistence.js')(config);
|
||||
|
||||
// server.run(config, db);
|
||||
persistence.LoadDB().then(db => {
|
||||
console.log("Loaded DB.");
|
||||
return server.run(db);
|
||||
}).then((port) => {
|
||||
console.log("Server is running on: ", port);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to run server: ", error);
|
||||
var errors = error;
|
||||
})
|
2251
package-lock.json
generated
Normal file
32
package.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "auth.sigidli.com",
|
||||
"version": "0.0.1",
|
||||
"description": "Site showing how hierarchically deterministic assymetric cryptographic keys can be used for authentication purposes.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"hierarchically",
|
||||
"deterministic"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bcrypt": "^3.0.6",
|
||||
"body-parser": "^1.19.0",
|
||||
"config": "^3.2.3",
|
||||
"connect-session-sequelize": "^6.0.0",
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.0",
|
||||
"moment": "^2.24.0",
|
||||
"nanoid": "^2.1.6",
|
||||
"passport": "^0.4.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"pug": "^2.0.4",
|
||||
"qrcode": "^1.4.4",
|
||||
"sequelize": "^5.21.1",
|
||||
"sqlite3": "^4.1.0",
|
||||
"tunnel-ssh": "^4.1.4"
|
||||
}
|
||||
}
|
30
persistence/models/authenticationMethod.js
Normal file
@ -0,0 +1,30 @@
|
||||
module.exports = function (sequelize, DataTypes, options) {
|
||||
const model = sequelize.define("authenticationMethod", {
|
||||
id: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: function() {
|
||||
return options.generateUniqueId()
|
||||
},
|
||||
primaryKey: true,
|
||||
unique: true
|
||||
},
|
||||
method: {
|
||||
type: DataTypes.ENUM,
|
||||
values: ['hd-auth', 'password', 'google-oauth']
|
||||
},
|
||||
order: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: function() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
comment: "The supported authentication methods for a user of the system."
|
||||
});
|
||||
|
||||
model.associate = (db) => {
|
||||
model.User = model.belongsTo(db.User);
|
||||
}
|
||||
|
||||
return model;
|
||||
};
|
25
persistence/models/extendedPublicKey.js
Normal file
@ -0,0 +1,25 @@
|
||||
module.exports = function (sequelize, DataTypes, options) {
|
||||
const model = sequelize.define("extendedPublicKey", {
|
||||
id: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: function() {
|
||||
return options.generateUniqueId()
|
||||
},
|
||||
primaryKey: true,
|
||||
unique: true
|
||||
},
|
||||
xpub: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
}
|
||||
}, {
|
||||
comment: "Extended Public Key belonging to the users of the system"
|
||||
});
|
||||
|
||||
model.associate = (db) => {
|
||||
model.User = model.belongsTo(db.User);
|
||||
}
|
||||
|
||||
return model;
|
||||
};
|
36
persistence/models/password.js
Normal file
@ -0,0 +1,36 @@
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
module.exports = function (sequelize, DataTypes, options) {
|
||||
const model = sequelize.define("password", {
|
||||
id: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: function() {
|
||||
return options.generateUniqueId()
|
||||
},
|
||||
primaryKey: true,
|
||||
unique: true
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING,
|
||||
// primaryKey: true,
|
||||
unique: true,
|
||||
allowNull: false,
|
||||
set(val) {
|
||||
var hashedPassword = options != null && val != null && val.length > 0 ?
|
||||
bcrypt.hashSync(val, bcrypt.genSaltSync(11)) :
|
||||
null
|
||||
;
|
||||
this.setDataValue('password', hashedPassword);
|
||||
}
|
||||
// should have an accessor method the redacts this failed...
|
||||
}
|
||||
}, {
|
||||
comment: "hashed password"
|
||||
});
|
||||
|
||||
model.associate = (db) => {
|
||||
model.User = model.belongsTo(db.User);
|
||||
}
|
||||
|
||||
return model;
|
||||
};
|
19
persistence/models/session.js
Normal file
@ -0,0 +1,19 @@
|
||||
module.exports = function (sequelize, DataTypes, options) {
|
||||
var model = sequelize.define("session", {
|
||||
sid: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true,
|
||||
unique: true,
|
||||
},
|
||||
expires: DataTypes.DATE,
|
||||
data: DataTypes.STRING(50000)
|
||||
}, {
|
||||
comment: "Each session in the system will be stored in this table..."
|
||||
});
|
||||
|
||||
model.associate = (db) => {
|
||||
model.User = model.belongsTo(db.User);
|
||||
}
|
||||
|
||||
return model;
|
||||
};
|
28
persistence/models/user.js
Normal file
@ -0,0 +1,28 @@
|
||||
module.exports = function (sequelize, DataTypes, options) {
|
||||
const model = sequelize.define("user", {
|
||||
id: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: function() {
|
||||
return options.generateUniqueId()
|
||||
},
|
||||
primaryKey: true,
|
||||
unique: true
|
||||
},
|
||||
|
||||
displayName: {
|
||||
type: DataTypes.STRING,
|
||||
// allowNull: false
|
||||
}
|
||||
}, {
|
||||
comment: "The individual users of the system..."
|
||||
});
|
||||
|
||||
model.associate = (db) => {
|
||||
model.Sessions = model.hasMany(db.Session);
|
||||
model.ExtendedPublicKeys = model.hasMany(db.ExtendedPublicKey);
|
||||
model.Passwords = model.hasMany(db.Password);
|
||||
model.AuthenticationMethods = model.hasMany(db.AuthenticationMethod);
|
||||
}
|
||||
|
||||
return model;
|
||||
};
|
128
persistence/persistence.js
Normal file
@ -0,0 +1,128 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function () {
|
||||
var db = {};
|
||||
|
||||
const config = require('config');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const nanoid = require('nanoid');
|
||||
const tunnel = require('tunnel-ssh');
|
||||
|
||||
var dbOptions = {
|
||||
db: db,
|
||||
moment: require('moment'),
|
||||
generateUniqueId: (customIdSize) => {
|
||||
var idSize = customIdSize || 11;
|
||||
return nanoid(idSize); //=> "hsCc0ocrXkc"
|
||||
}
|
||||
};
|
||||
|
||||
dbOptions.Sequelize = require('sequelize');
|
||||
dbOptions.bcrypt = require('bcrypt');
|
||||
|
||||
dbOptions.sequelize = new dbOptions.Sequelize(config.get('database'));
|
||||
|
||||
// Load all the models...
|
||||
var modelDir = path.join(__dirname, './models/');
|
||||
fs.readdirSync(modelDir)
|
||||
.filter(file => {
|
||||
return (file.indexOf('.') !== 0) && (file !== "index.js") && (file.slice(-3) === '.js');
|
||||
})
|
||||
.forEach(file => {
|
||||
// const model = sequelize['import'](path.join(modelDir, file));
|
||||
const model = require(path.join(modelDir, file))(dbOptions.sequelize, dbOptions.Sequelize, dbOptions);
|
||||
|
||||
// Format the Model Name like we have for this project...
|
||||
var modelName = model.name;
|
||||
modelName = modelName.replace(modelName[0], modelName[0].toUpperCase());
|
||||
|
||||
// Add model to the db...
|
||||
db[modelName] = model;
|
||||
});
|
||||
|
||||
// Time for the associations...
|
||||
|
||||
Object.keys(db).forEach(modelName => {
|
||||
if (db[modelName].associate) {
|
||||
console.log("Loading associations for " + modelName);
|
||||
db[modelName].associate(db);
|
||||
}
|
||||
});
|
||||
|
||||
db.LoadDB = () => {
|
||||
db.Sequelize = dbOptions.Sequelize;
|
||||
db.sequelize = dbOptions.sequelize;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
tunneling()
|
||||
.then(() => {
|
||||
return dbOptions.sequelize.sync()
|
||||
}).then(() => {
|
||||
resolve(db);
|
||||
}).catch(error => {
|
||||
// Do the things...
|
||||
reject(error);
|
||||
console.error('Unable to connect to the database:', error);
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
db.updateDatabaseFromModels = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
dbOptions.sequelize.sync({
|
||||
alter: true
|
||||
})
|
||||
.then(() => {
|
||||
resolve(true);
|
||||
console.log("DB update successful.");
|
||||
})
|
||||
.catch(error => {
|
||||
// Do the things...
|
||||
reject(error);
|
||||
console.error('Unable to connect to the database:', error);
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
var tunneling = () => {
|
||||
const tunnelConfigs = config.get("tunnel");
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if(tunnelConfigs.useTunnel) {
|
||||
const privateKeyFilePath = path.resolve(tunnelConfigs["privateKeyFilePath"]);
|
||||
// Run the tunnel...
|
||||
var sshConfig = {
|
||||
"keepAlive": tunnelConfigs["keepAlive"],
|
||||
"username": tunnelConfigs["username"],
|
||||
// "password": tunnelConfigs["password"],
|
||||
"host": tunnelConfigs["host"],
|
||||
"port": tunnelConfigs["port"],
|
||||
"dstHost": tunnelConfigs["dstHost"],
|
||||
"dstPort": tunnelConfigs["dstPort"],
|
||||
// "localHost": tunnelConfigs["localHost"],
|
||||
"localPort": tunnelConfigs["localPort"],
|
||||
"privateKey": fs.readFileSync(privateKeyFilePath),
|
||||
"passphrase": tunnelConfigs["passphrase"]
|
||||
}
|
||||
console.log("Private Key path: ", privateKeyFilePath);
|
||||
tunnel(sshConfig, (error, server) => {
|
||||
if(error) {
|
||||
console.error("Tunneling: ", error);
|
||||
reject(error);
|
||||
} else {
|
||||
console.log('Tunnel server:', server);
|
||||
resolve(server)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Continue without setting up a tunnel...
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
}
|
||||
return db;
|
||||
};
|
||||
|
279
server/router/account/index.js
Normal file
@ -0,0 +1,279 @@
|
||||
/**
|
||||
* This router handles things related to the web browser experience...
|
||||
*/
|
||||
// This is the mock data we working with...
|
||||
|
||||
module.exports = function (options) {
|
||||
const config = require('config');
|
||||
var QRCode = require('qrcode');
|
||||
var express = options.express;
|
||||
|
||||
const db = options.db;
|
||||
const hdAuthUtil = options.hdAuthUtil;
|
||||
|
||||
var router = express.Router();
|
||||
|
||||
router.route('/')
|
||||
.get(function(request, response, next) {
|
||||
response.render("home", {
|
||||
user: request.user,
|
||||
pageTitle: "HDAuth - Account"
|
||||
})
|
||||
});
|
||||
|
||||
router.route('/authenticate')
|
||||
.get(function(request, response, next) {
|
||||
if(request.user) {
|
||||
response.redirect('/account');
|
||||
} else {
|
||||
response.render("login-displayName", {
|
||||
user: request.user,
|
||||
pageTitle: "HDAuth - Login"
|
||||
})
|
||||
}
|
||||
})
|
||||
.post(function(request, response, next) {
|
||||
if(request.user) {
|
||||
response.redirect('/account');
|
||||
} else {
|
||||
db.User.findOne({
|
||||
where: {
|
||||
displayName: request.body.displayName
|
||||
},
|
||||
include: [
|
||||
{
|
||||
association: db.User.ExtendedPublicKeys,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}).then(user => {
|
||||
if(user) {
|
||||
// TODO: Support multiple login options...
|
||||
const loginMessage = {
|
||||
userIdentifier: user.displayName,
|
||||
domainUrl: "sigidli.com" // TODO: Replace with config value...
|
||||
}
|
||||
const challenge = hdAuthUtil.createChallenge(
|
||||
user.extendedPublicKeys[0].xpub,
|
||||
JSON.stringify(loginMessage) // TODO: Create a login body...
|
||||
)
|
||||
QRCode.toDataURL(JSON.stringify(challenge), function (err, url) {
|
||||
if(err) {
|
||||
console.error(err);
|
||||
}
|
||||
response.render("xpub-login", {
|
||||
// challenge user to sign thier xpub
|
||||
challenge: challenge,
|
||||
qrCode: url,
|
||||
user: request.user
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// User doesn't exist register account
|
||||
response.render("login-signup", {
|
||||
user: request.user,
|
||||
displayName: request.body.displayName,
|
||||
pageTitle: "HD Auth - Signup",
|
||||
});
|
||||
}
|
||||
|
||||
}).catch(error => {
|
||||
console.error("Failed to fulfill account/authenticate post", request.body);
|
||||
console.error("Reason: ", error);
|
||||
|
||||
response.render("error", {
|
||||
message: "This site didn't have the time for error handling."
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
router.route('/authenticate/response')
|
||||
.post(function(request, response, next) {
|
||||
if(request.user) {
|
||||
response.redirect('/account');
|
||||
} else {
|
||||
// Verify challenge
|
||||
if(hdAuthUtil.verifyHDAuthChallengeResponse(request.body)) {
|
||||
// user passed challenge...
|
||||
var loginMessage = JSON.parse(request.body.message);
|
||||
db.User.findOne({
|
||||
where: {
|
||||
displayName: loginMessage.userIdentifier,
|
||||
},
|
||||
include: [
|
||||
{
|
||||
association: db.User.ExtendedPublicKeys
|
||||
}
|
||||
]
|
||||
}).then(user => {
|
||||
if(user) {
|
||||
// User created we can authenticate them on the site...
|
||||
request.logIn(user, function(err) {
|
||||
if (err) { return next(err); }
|
||||
return response.redirect('/');
|
||||
});
|
||||
} else {
|
||||
console.error("Authenticated user doesn't exist: ", loginMessage);
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error("Failed to create authenticated user");
|
||||
console.error("Error: ", error);
|
||||
next(error);
|
||||
})
|
||||
} else {
|
||||
// user failed challenge
|
||||
console.error("User failed to authenticate");
|
||||
// Create new challenge and try again...
|
||||
return response.redirect('/authenticate');
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
router.route('/register')
|
||||
.get(function(request, response, next) {
|
||||
if(request.user) {
|
||||
response.redirect('/account');
|
||||
} else {
|
||||
response.render("account-registration", {
|
||||
user: request.user,
|
||||
newUser: {},
|
||||
pageTitle: "HDAuth - Registration"
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
router.route('/register/xpub')
|
||||
.post(function(request, response, next) {
|
||||
if(request.user) {
|
||||
response.redirect('/account');
|
||||
} else {
|
||||
db.User.build({
|
||||
displayName: request.body.displayName,
|
||||
extendedPublicKeys: [
|
||||
{
|
||||
xpub: request.body.xpub
|
||||
}
|
||||
]
|
||||
}, {
|
||||
include: [
|
||||
{
|
||||
association: db.User.ExtendedPublicKeys
|
||||
}
|
||||
]
|
||||
}).validate()
|
||||
.then(xpubUser => {
|
||||
if (xpubUser) {
|
||||
// Challenge
|
||||
const registerationMessage = {
|
||||
userIdentifier: xpubUser.displayName, // identifier the user supplies on the login page
|
||||
user: xpubUser,
|
||||
serviceExtendedPublicKey: config.get("bip32.serviceAuthenticatingExtendedPublicKey"),
|
||||
url: "sigidli.com"
|
||||
}
|
||||
const challenge = hdAuthUtil.createChallenge(
|
||||
request.body.xpub, // update this to identifier...
|
||||
JSON.stringify(registerationMessage)
|
||||
)
|
||||
QRCode.toDataURL(JSON.stringify(challenge), function (err, url) {
|
||||
if(err) {
|
||||
console.error(err);
|
||||
}
|
||||
response.render("xpub-account-registeration-challenge", {
|
||||
xpubUser: xpubUser,
|
||||
// challenge user to sign thier xpub
|
||||
challenge: challenge,
|
||||
qrCode: url,
|
||||
user: request.user
|
||||
})
|
||||
})
|
||||
|
||||
} else {
|
||||
console.log("Empty User")
|
||||
|
||||
// TODO: error handling..
|
||||
response.render("error", {
|
||||
user: request.user,
|
||||
message: "VOID"
|
||||
})
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error("Failed to create user");
|
||||
console.error("Reason: ", error);
|
||||
|
||||
// TODO: error handling..
|
||||
response.render("error", {
|
||||
user: request.user,
|
||||
message: "Failed to create user"
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
router.route('/register/xpub/response')
|
||||
.post(function(request, response, next) {
|
||||
if(request.user) {
|
||||
response.redirect('/account');
|
||||
} else {
|
||||
// Verify challenge
|
||||
if(hdAuthUtil.verifyHDAuthChallengeResponse(request.body)) {
|
||||
// user passed challenge...
|
||||
// TODO: Load registration request from challenge.message
|
||||
// TODO: build and validate that user owns xpub...
|
||||
var xpubUser = JSON.parse(request.body.message).user;
|
||||
// Possibility that username is taken...
|
||||
// TODO: Create user without username
|
||||
db.User.create(xpubUser, {
|
||||
include: [
|
||||
{
|
||||
association: db.User.ExtendedPublicKeys
|
||||
}
|
||||
]
|
||||
}).then(user => {
|
||||
if(user) {
|
||||
// User created we can authenticate them on the site...
|
||||
request.logIn(user, function(err) {
|
||||
if (err) { return next(err); }
|
||||
return response.redirect('/');
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error("Failed to create authenticated user");
|
||||
console.error("Error: ", error);
|
||||
next(error);
|
||||
})
|
||||
} else {
|
||||
// user failed challenge
|
||||
// TODO: Validate input
|
||||
// TODO: Createa new challenge
|
||||
console.error("User failed to authenticate");
|
||||
// Create new challenge and try again...
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
router.route('/logout')
|
||||
.post(function(request, response, next) {
|
||||
if(request.user) {
|
||||
request.logout();
|
||||
response.redirect('/');
|
||||
|
||||
// db.Session.destroy({
|
||||
// where: {
|
||||
// sid: request.sessionID
|
||||
// }
|
||||
// }).then(() => {
|
||||
// console.log("")
|
||||
// }).catch(error => {
|
||||
// next(error);
|
||||
// console.error("Log out error: ", error);
|
||||
// })
|
||||
} else {
|
||||
// No user...
|
||||
response.redirect('/');
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: add other endpoints
|
||||
return router;
|
||||
};
|
28
server/router/index.js
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* This router handles things related to the web browser experience...
|
||||
*/
|
||||
// This is the mock data we working with...
|
||||
|
||||
module.exports = function (options) {
|
||||
|
||||
var express = options.express;
|
||||
|
||||
const db = options.db;
|
||||
|
||||
var router = express.Router();
|
||||
|
||||
router.route('/')
|
||||
.get(function(request, response, next) {
|
||||
response.render("home", {
|
||||
user: request.user,
|
||||
pageTitle: "HDAuth - Home"
|
||||
})
|
||||
});
|
||||
|
||||
// TODO: load child routers automatically
|
||||
var accountRouter = require('./account/index.js')(options);
|
||||
|
||||
router.use('/account', accountRouter);
|
||||
|
||||
return router;
|
||||
};
|
119
server/server.js
Normal file
@ -0,0 +1,119 @@
|
||||
module.exports = function () {
|
||||
var server = {};
|
||||
|
||||
server.run = (db) => {
|
||||
console.log("Starting Server")
|
||||
return new Promise((resolve, reject) => {
|
||||
const config = require('config');
|
||||
const express = require('express');
|
||||
const pug = require("pug");
|
||||
const session = require("express-session");
|
||||
const SequelizeSessionStore = require('connect-session-sequelize')(session.Store);
|
||||
const bodyParser = require("body-parser");
|
||||
const path = require("path");
|
||||
|
||||
const passport = require('passport');
|
||||
const HDAuthStrategy = require('../../passport-hd-auth').Strategy;
|
||||
|
||||
const app = express();
|
||||
|
||||
app.set("view engine", "pug");
|
||||
app.set("views", path.resolve("server/views"));
|
||||
|
||||
app.use('/static', express.static("server/static"));
|
||||
|
||||
// Session related stuff...
|
||||
function extendDefaultFields(defaults, session) {
|
||||
return {
|
||||
data: defaults.data,
|
||||
expires: session.cookie && session.cookie.expires ? session.cookie.expires : defaults.expires, // 157680000
|
||||
userId: session.passport.user
|
||||
};
|
||||
}
|
||||
|
||||
var sessionStore = new SequelizeSessionStore({
|
||||
db: db.sequelize,
|
||||
table: 'session',
|
||||
extendDefaultFields: extendDefaultFields
|
||||
// TODO: Define expiry and clean up...
|
||||
});
|
||||
|
||||
app.use(session({
|
||||
store: sessionStore,
|
||||
secret: config.get("server.secret"),
|
||||
saveUninitialized: false,
|
||||
cookie: config.get("server.cookie")
|
||||
}));
|
||||
sessionStore.sync();
|
||||
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
// Setup all passport strategies
|
||||
const hdAuthUtil = new HDAuthStrategy({
|
||||
serverExtendedPrivateKey: config.get('bip32.serverExtendedPrivateKey'),
|
||||
serverExtendedPrivateKeyDerivationPath: config.get('bip32.serverExtendedPrivateKeyDerivationPath'),
|
||||
serviceAuthenticatingExtendedPublicKey: config.get('bip32.serviceAuthenticatingExtendedPublicKey')
|
||||
}, function(xpub, challengeRequestDerivationPath, challengeRequestMessage, challengeResponseSignature, done) {
|
||||
db.User.findOne({
|
||||
include: [
|
||||
{
|
||||
association: db.User.ExtendedPublicKeys,
|
||||
required: true,
|
||||
where: {
|
||||
xpub: xpub
|
||||
}
|
||||
}
|
||||
]
|
||||
}).then(user => {
|
||||
if(user) {
|
||||
done(null, user, {verified: true})
|
||||
} else {
|
||||
done(null, false);
|
||||
}
|
||||
}).catch(error => {
|
||||
done(error);
|
||||
})
|
||||
});
|
||||
passport.use(hdAuthUtil); // TODO: implement new hdAuthUtil.Strategy()
|
||||
|
||||
passport.serializeUser(function(user, done) {
|
||||
done(null, user.id);
|
||||
});
|
||||
|
||||
passport.deserializeUser(function(id, done) {
|
||||
// TODO: Add memberships and things like that...
|
||||
return db.User.findByPk(id) // TODO: Limit attributes...
|
||||
.then(user => {
|
||||
return done(null, user);
|
||||
}).catch(error => {
|
||||
return done(error);
|
||||
})
|
||||
});
|
||||
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
// TODO: Create a load router module... that has inheritence...
|
||||
var router = require('./router/index.js')({
|
||||
app: app,
|
||||
express: express,
|
||||
db: db,
|
||||
passport: passport,
|
||||
hdAuthUtil: hdAuthUtil
|
||||
});
|
||||
// REGISTER OUR ROUTES -------------------------------
|
||||
// all of our routes will be prefixed with /api
|
||||
app.use('/', router);
|
||||
|
||||
const port = process.env.PORT || config.get("server.port");
|
||||
|
||||
app.listen(port);
|
||||
resolve(port);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: other features I might wanna do in the server...
|
||||
|
||||
return server;
|
||||
};
|
56
server/views/account-registration.pug
Normal file
@ -0,0 +1,56 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
.container
|
||||
.center
|
||||
.row
|
||||
.col.s12
|
||||
h4 HD Auth Account Registration
|
||||
.row
|
||||
.col.s12.m1
|
||||
.col.s12.m10
|
||||
if error && error.message
|
||||
.row
|
||||
.col.s12
|
||||
p.flow-text.red-text= error.message
|
||||
//- img.materialboxed(width="100%", src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Wikidata_logo_under_construction_sign_diamond.svg/1920px-Wikidata_logo_under_construction_sign_diamond.svg.png", alt="Under Construction")
|
||||
form(method="post")
|
||||
//- input(type="hidden", name="email", value=email)
|
||||
.row.center
|
||||
.input-field.col.s12.m6
|
||||
input(name="displayName", type="text", value=newUser.displayName, placeholder="Unique displayName", autofocus)
|
||||
label(for="displayName") Display Name
|
||||
.input-field.col.s12.m6
|
||||
input(name="email", value=newUser.email, placeholder="email useful for account recovery", type="text")
|
||||
label(for="email") Optional email
|
||||
.row
|
||||
|
||||
.col.s12
|
||||
ul.tabs
|
||||
li.tab.col.s6
|
||||
a.active(href="#xpub-tab") with Extended Public Key
|
||||
li.tab.col.s6
|
||||
a(href="#password-tab") with Password
|
||||
#xpub-tab.col.s12
|
||||
.row
|
||||
.row
|
||||
.input-field.col.s12
|
||||
input(name="xpub", type="text", placeholder="Extended Public")
|
||||
label(for="xpub") Extended Public Key
|
||||
//- TODO: Challenge to sign the xpub...
|
||||
.row.center
|
||||
.col.s12
|
||||
button.btn(type="submit", formaction="/account/register/xpub") Register with Extended Public Key
|
||||
#password-tab.col.s12
|
||||
.row
|
||||
.row
|
||||
.input-field.col.s12.m6
|
||||
input(name="password", type="password", placeholder="Unique password")
|
||||
label(for="password") Password
|
||||
.input-field.col.s12.m6
|
||||
input(name="confirmPassword", type="password", placeholder="Confirm your unique password")
|
||||
label(for="confirmPassword") Confirm Password
|
||||
.row.center
|
||||
.col.s12
|
||||
button.btn(type="submit", formaction="/account/register/password") Register with Password
|
||||
.col.s12.m1
|
31
server/views/css/materialize.min.css
vendored
Normal file
20
server/views/css/site-layout.css
Normal file
@ -0,0 +1,20 @@
|
||||
body {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
header, main, footer {
|
||||
padding-left: 300px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width : 992px) {
|
||||
header, main, footer {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
0
server/views/css/site.css
Normal file
9
server/views/error.pug
Normal file
@ -0,0 +1,9 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
.container
|
||||
//- .row
|
||||
h1 That request failed to compile :(
|
||||
|
||||
if message
|
||||
p.flow-text= message
|
6
server/views/home.pug
Normal file
@ -0,0 +1,6 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
.container
|
||||
//- .row
|
||||
h1 Welcome to the World
|
1
server/views/img/account_circle.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm0 6c3.31 0 6 2.69 6 6 0 3.32-2.69 6-6 6s-6-2.68-6-6c0-3.31 2.69-6 6-6zm0 28.4c-5.01 0-9.41-2.56-12-6.44.05-3.97 8.01-6.16 12-6.16s11.94 2.19 12 6.16c-2.59 3.88-6.99 6.44-12 6.44z"/></svg>
|
After Width: | Height: | Size: 325 B |
1
server/views/img/close.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M38 12.83L35.17 10 24 21.17 12.83 10 10 12.83 21.17 24 10 35.17 12.83 38 24 26.83 35.17 38 38 35.17 26.83 24z"/></svg>
|
After Width: | Height: | Size: 210 B |
1
server/views/img/code.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M18.8 33.2L9.7 24l9.2-9.2L16 12 4 24l12 12 2.8-2.8zm10.4 0l9.2-9.2-9.2-9.2L32 12l12 12-12 12-2.8-2.8z"/></svg>
|
After Width: | Height: | Size: 179 B |
1
server/views/img/directions_run.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M27 9c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4zm-7.2 27.8l1.9-8.8 4.3 4v12h4V28.9l-4.1-4.1 1.2-6C29.7 22 33.6 24 38 24v-4c-3.7 0-6.9-2-8.7-4.9l-1.9-3.2c-.7-1.2-2-1.9-3.4-1.9-.5 0-1 .1-1.5.3L12 14.6V24h4v-6.7l3.5-1.4L16.4 32l-9.8-1.9-.8 3.9s14 2.7 14 2.8z"/></svg>
|
After Width: | Height: | Size: 338 B |
1
server/views/img/file_download.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M38 18h-8V6H18v12h-8l14 14 14-14zM10 36v4h28v-4H10z"/></svg>
|
After Width: | Height: | Size: 129 B |
1
server/views/img/file_upload.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M18 32h12V20h8L24 6 10 20h8zm-8 4h28v4H10z"/></svg>
|
After Width: | Height: | Size: 120 B |
4
server/views/img/fullscreen.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
|
||||
<path d="M0 0h48v48H0z" fill="none"/>
|
||||
<path d="M14 28h-4v10h10v-4h-6v-6zm-4-8h4v-6h6v-4H10v10zm24 14h-6v4h10V28h-4v6zm-6-24v4h6v6h4V10H28z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 241 B |
4
server/views/img/home.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
|
||||
<path d="M20 40V28h8v12h10V24h6L24 6 4 24h6v16z"/>
|
||||
<path d="M0 0h48v48H0z" fill="none"/>
|
||||
</svg>
|
After Width: | Height: | Size: 188 B |
1
server/views/img/menu.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M6 36h36v-4H6v4zm0-10h36v-4H6v4zm0-14v4h36v-4H6z"/></svg>
|
After Width: | Height: | Size: 126 B |
1
server/views/img/more_vert.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M24 16c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 4c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 12c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z"/></svg>
|
After Width: | Height: | Size: 252 B |
4
server/views/img/pause.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
|
||||
<path d="M12 38h8V10h-8v28zm16-28v28h8V10h-8z"/>
|
||||
<path d="M0 0h48v48H0z" fill="none"/>
|
||||
</svg>
|
After Width: | Height: | Size: 163 B |
5
server/views/img/play_arrow.svg
Executable file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
|
||||
<path d="M-838-2232H562v3600H-838z" fill="none"/>
|
||||
<path d="M16 10v28l22-14z"/>
|
||||
<path d="M0 0h48v48H0z" fill="none"/>
|
||||
</svg>
|
After Width: | Height: | Size: 197 B |
4
server/views/img/radio.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
|
||||
<path d="M6.47 12.3C5.02 12.87 4 14.33 4 16v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V16c0-2.21-1.79-4-4-4H16.61l16.53-6.67L31.76 2 6.47 12.3zM14 40c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6zm26-16h-4v-4h-4v4H8v-8h32v8z"/>
|
||||
<path d="M0 0h48v48H0z" fill="none"/>
|
||||
</svg>
|
After Width: | Height: | Size: 347 B |
4
server/views/img/rotate_left.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
|
||||
<path d="M0 0h48v48H0z" fill="none"/>
|
||||
<path d="M14.22 17.05l-2.83-2.83c-1.8 2.32-2.91 5-3.25 7.78h4.04c.29-1.75.97-3.44 2.04-4.95zM12.18 26H8.14c.34 2.78 1.45 5.46 3.25 7.78l2.83-2.83c-1.07-1.51-1.75-3.2-2.04-4.95zm2.02 10.63c2.32 1.81 5.02 2.88 7.8 3.22v-4.04c-1.75-.29-3.43-.98-4.93-2.05l-2.87 2.87zM26 8.14V2l-9.1 9.1L26 20v-7.82c5.67.95 10 5.88 10 11.82s-4.33 10.87-10 11.82v4.04c7.89-.99 14-7.7 14-15.86S33.89 9.13 26 8.14z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 508 B |
4
server/views/img/rotate_right.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
|
||||
<path d="M0 0h48v48H0z" fill="none"/>
|
||||
<path d="M31.1 11.1L22 2v6.14C14.11 9.12 8 15.84 8 24s6.11 14.88 14 15.86v-4.04c-5.67-.95-10-5.88-10-11.82s4.33-10.87 10-11.82V20l9.1-8.9zM39.86 22c-.34-2.78-1.45-5.46-3.25-7.78l-2.83 2.83c1.07 1.51 1.75 3.2 2.04 4.95h4.04zM26 35.81v4.05c2.78-.34 5.48-1.42 7.8-3.22l-2.87-2.87c-1.5 1.06-3.18 1.74-4.93 2.04zm7.78-4.86l2.83 2.83c1.8-2.32 2.91-5 3.25-7.78h-4.04c-.29 1.75-.97 3.44-2.04 4.95z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 507 B |
1
server/views/img/search.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M31 28h-1.59l-.55-.55C30.82 25.18 32 22.23 32 19c0-7.18-5.82-13-13-13S6 11.82 6 19s5.82 13 13 13c3.23 0 6.18-1.18 8.45-3.13l.55.55V31l10 9.98L40.98 38 31 28zm-12 0c-4.97 0-9-4.03-9-9s4.03-9 9-9 9 4.03 9 9-4.03 9-9 9z"/></svg>
|
After Width: | Height: | Size: 294 B |
4
server/views/img/send.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
|
||||
<path d="M4.02 42L46 24 4.02 6 4 20l30 4-30 4z"/>
|
||||
<path d="M0 0h48v48H0z" fill="none"/>
|
||||
</svg>
|
After Width: | Height: | Size: 164 B |
1
server/views/img/settings.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M38.86 25.95c.08-.64.14-1.29.14-1.95s-.06-1.31-.14-1.95l4.23-3.31c.38-.3.49-.84.24-1.28l-4-6.93c-.25-.43-.77-.61-1.22-.43l-4.98 2.01c-1.03-.79-2.16-1.46-3.38-1.97L29 4.84c-.09-.47-.5-.84-1-.84h-8c-.5 0-.91.37-.99.84l-.75 5.3c-1.22.51-2.35 1.17-3.38 1.97L9.9 10.1c-.45-.17-.97 0-1.22.43l-4 6.93c-.25.43-.14.97.24 1.28l4.22 3.31C9.06 22.69 9 23.34 9 24s.06 1.31.14 1.95l-4.22 3.31c-.38.3-.49.84-.24 1.28l4 6.93c.25.43.77.61 1.22.43l4.98-2.01c1.03.79 2.16 1.46 3.38 1.97l.75 5.3c.08.47.49.84.99.84h8c.5 0 .91-.37.99-.84l.75-5.3c1.22-.51 2.35-1.17 3.38-1.97l4.98 2.01c.45.17.97 0 1.22-.43l4-6.93c.25-.43.14-.97-.24-1.28l-4.22-3.31zM24 31c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"/></svg>
|
After Width: | Height: | Size: 764 B |
4
server/views/img/share.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
|
||||
<path d="M0 0h48v48H0z" fill="none"/>
|
||||
<path d="M36 32.17c-1.52 0-2.89.59-3.93 1.54L17.82 25.4c.11-.45.18-.92.18-1.4s-.07-.95-.18-1.4l14.1-8.23c1.07 1 2.5 1.62 4.08 1.62 3.31 0 6-2.69 6-6s-2.69-6-6-6-6 2.69-6 6c0 .48.07.95.18 1.4l-14.1 8.23c-1.07-1-2.5-1.62-4.08-1.62-3.31 0-6 2.69-6 6s2.69 6 6 6c1.58 0 3.01-.62 4.08-1.62l14.25 8.31c-.1.42-.16.86-.16 1.31 0 3.22 2.61 5.83 5.83 5.83s5.83-2.61 5.83-5.83-2.61-5.83-5.83-5.83z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 503 B |
1
server/views/img/thumb_down.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M30 6H12c-1.66 0-3.08 1.01-3.68 2.44l-6.03 14.1C2.11 23 2 23.49 2 24v3.83l.02.02L2 28c0 2.21 1.79 4 4 4h12.63l-1.91 9.14c-.04.2-.07.41-.07.63 0 .83.34 1.58.88 2.12L19.66 46l13.17-13.17C33.55 32.1 34 31.1 34 30V10c0-2.21-1.79-4-4-4zm8 0v24h8V6h-8z"/></svg>
|
After Width: | Height: | Size: 324 B |
1
server/views/img/thumb_up.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M2 42h8V18H2v24zm44-22c0-2.21-1.79-4-4-4H29.37l1.91-9.14c.04-.2.07-.41.07-.63 0-.83-.34-1.58-.88-2.12L28.34 2 15.17 15.17C14.45 15.9 14 16.9 14 18v20c0 2.21 1.79 4 4 4h18c1.66 0 3.08-1.01 3.68-2.44l6.03-14.1c.18-.46.29-.95.29-1.46v-3.83l-.02-.02L46 20z"/></svg>
|
After Width: | Height: | Size: 330 B |
1
server/views/img/twitter_white_logo.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg id="Logo_FIXED" data-name="Logo — FIXED" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400"><defs><style>.cls-1{fill:none;}.cls-2{fill:#ffffff;}</style></defs><title>Twitter_Logo_Blue</title><rect class="cls-1" width="400" height="400"/><path class="cls-2" d="M153.62,301.59c94.34,0,145.94-78.16,145.94-145.94,0-2.22,0-4.43-.15-6.63A104.36,104.36,0,0,0,325,122.47a102.38,102.38,0,0,1-29.46,8.07,51.47,51.47,0,0,0,22.55-28.37,102.79,102.79,0,0,1-32.57,12.45,51.34,51.34,0,0,0-87.41,46.78A145.62,145.62,0,0,1,92.4,107.81a51.33,51.33,0,0,0,15.88,68.47A50.91,50.91,0,0,1,85,169.86c0,.21,0,.43,0,.65a51.31,51.31,0,0,0,41.15,50.28,51.21,51.21,0,0,1-23.16.88,51.35,51.35,0,0,0,47.92,35.62,102.92,102.92,0,0,1-63.7,22A104.41,104.41,0,0,1,75,278.55a145.21,145.21,0,0,0,78.62,23"/></svg>
|
After Width: | Height: | Size: 790 B |
7
server/views/js/clipboard.min.js
vendored
Normal file
15
server/views/js/copy-to-clipboard.js
Normal file
@ -0,0 +1,15 @@
|
||||
var clipboard = new ClipboardJS('.copy-to-clipboard');
|
||||
|
||||
clipboard.on('success', function(e) {
|
||||
console.info('Action:', e.action);
|
||||
console.info('Text:', e.text);
|
||||
console.info('Trigger:', e.trigger);
|
||||
|
||||
e.clearSelection();
|
||||
M.toast({html: 'copied'})
|
||||
});
|
||||
|
||||
clipboard.on('error', function(e) {
|
||||
console.error('Action:', e.action);
|
||||
console.error('Trigger:', e.trigger);
|
||||
});
|
2
server/views/js/jquery-3.3.1.min.js
vendored
Normal file
12374
server/views/js/materialize.js
vendored
Normal file
6
server/views/js/materialize.min.js
vendored
Normal file
9
server/views/js/site.js
Normal file
@ -0,0 +1,9 @@
|
||||
// SITE WIDE Javascript...
|
||||
$(document).ready(function() {
|
||||
$('.sidenav').sidenav();
|
||||
$('.tabs').tabs({
|
||||
onShow: function () {
|
||||
// TODO: remove input values from other tabs...
|
||||
}
|
||||
});
|
||||
})
|
39
server/views/login-displayName.pug
Normal file
@ -0,0 +1,39 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
-
|
||||
if (data)
|
||||
var message = data.message
|
||||
data = data == null ? {} : data;
|
||||
|
||||
.container
|
||||
.row
|
||||
.col.s12
|
||||
.row
|
||||
.col.s12.l4.m3
|
||||
p
|
||||
.col.s12.l4.m6
|
||||
.center
|
||||
h2.flow-text Login/Register
|
||||
if message
|
||||
p.flow-text.red-text= message
|
||||
form(action="/account/authenticate" method="POST")
|
||||
.row
|
||||
<label for="displayName">Display Name:</label>
|
||||
<input id="displayName" type="text" name="displayName" value="#{data.displayName}" autofocus/>
|
||||
.row
|
||||
|
||||
<div class="col s12 center">
|
||||
//- <button class="btn waves-effect waves-light" v-on:click="attemptLogin">Log In</button>
|
||||
<button class="btn blue waves-effect waves-light" type="submit" name="action">Submit
|
||||
</button>
|
||||
</div>
|
||||
.col.s12.l4.m3
|
||||
p
|
||||
//- p.center
|
||||
a.center(href="/reset-password") forgot password
|
||||
//- .col.s12
|
||||
.center
|
||||
p
|
||||
strong OR
|
||||
a.btn.blue(href="/account/oauth/google") LOGIN VIA GOOGLE
|
28
server/views/login-signup.pug
Normal file
@ -0,0 +1,28 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
-
|
||||
if (data)
|
||||
var message = data.message
|
||||
data = data == null ? {} : data;
|
||||
.container
|
||||
.center
|
||||
if displayName
|
||||
h4 Your displayName
|
||||
h3 #{displayName}
|
||||
else
|
||||
h4 Your email
|
||||
h3 #{email}
|
||||
h4 isn't linked to a HD Auth account.
|
||||
p.flow-text Wanna join?
|
||||
if message
|
||||
p.flow-text.red-text= message
|
||||
|
||||
a.btn.blue(href="/account/register") Create Account
|
||||
//- .center
|
||||
.row
|
||||
.col.s12
|
||||
p
|
||||
strong OR
|
||||
a.btn.blue(href="/account/authenticate/email") LOGIN VIA email
|
||||
|
92
server/views/templates/layout.pug
Normal file
@ -0,0 +1,92 @@
|
||||
|
||||
doctype html
|
||||
html(lang="en" dir="ltr")
|
||||
head
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
|
||||
<link async href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
style(media="screen")
|
||||
include ../css/site-layout.css
|
||||
style(media="screen")
|
||||
include ../css/materialize.min.css
|
||||
style(media="screen")
|
||||
include ../css/site.css
|
||||
//- style(media="screen")
|
||||
include ../css/typography.css
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
|
||||
<link rel="manifest" href="static/js/manifest.json">
|
||||
block additionalStyle
|
||||
|
||||
body
|
||||
ul.sidenav.sidenav-fixed#slide-out
|
||||
li
|
||||
.user-view
|
||||
.background
|
||||
img(src="static/images/office.jpg", alt="background")
|
||||
if user
|
||||
a#name
|
||||
span.white-text.name= user.displayName
|
||||
if user
|
||||
li
|
||||
form.center(action="/account/logout" method="POST")
|
||||
<button class="btn-flat" type="submit">logout</button>
|
||||
//- li.red-text
|
||||
a DELETE ACCOUNT
|
||||
else
|
||||
li
|
||||
a(href="/account/authenticate") login/register
|
||||
//- li
|
||||
a(href="/login/hd-auth") login with hd-auth
|
||||
|
||||
|
||||
|
||||
header(role="banner")
|
||||
block navigation
|
||||
nav
|
||||
.nav-wrapper
|
||||
a.brand-logo.center HD-Auth
|
||||
ul.left
|
||||
li
|
||||
a.sidenav-trigger(href="#", data-target="slide-out")
|
||||
i.material-icons menu
|
||||
|
||||
|
||||
main(role="main")
|
||||
.row
|
||||
.col.s12
|
||||
//- I just need the space at the top of the page to be good on mobile...
|
||||
block content
|
||||
.container
|
||||
p.flow-text Center content
|
||||
|
||||
block footer
|
||||
|
||||
footer.brand-colour.page-footer(role="footer")
|
||||
.container
|
||||
.row
|
||||
.col.s12
|
||||
p.flow-text footer content
|
||||
|
||||
.footer-copyright
|
||||
.container
|
||||
span © what year is it?
|
||||
strong Sigidli
|
||||
|
||||
//- TODO: Add a condition to use cdn in production...
|
||||
//- <script type="text/javascript" src="/static/js/jquery-3.3.1.min.js"></script>
|
||||
script
|
||||
include ../js/jquery-3.3.1.min.js
|
||||
//- <script type="text/javascript" src="/static/js/materialize.min.js"></script>
|
||||
script
|
||||
include ../js/materialize.min.js
|
||||
|
||||
//- <script type="text/javascript" src="/static/js/site.js"></script>
|
||||
script
|
||||
include ../js/site.js
|
||||
script
|
||||
include ../js/clipboard.min.js
|
||||
script
|
||||
include ../js/copy-to-clipboard.js
|
||||
block additionalScripts
|
66
server/views/xpub-account-registeration-challenge.pug
Normal file
@ -0,0 +1,66 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
-
|
||||
if (data)
|
||||
var message = data.message
|
||||
data = data == null ? {} : data;
|
||||
|
||||
.container
|
||||
.row
|
||||
.col.s12
|
||||
.row
|
||||
.col.s12
|
||||
.center
|
||||
h2.flow-text Authentication Challenge
|
||||
|
||||
.row
|
||||
.col.s12
|
||||
ul.tabs
|
||||
li.tab.s6
|
||||
a(href="#qr-code-tab") QR Code
|
||||
li.tab.s6
|
||||
a(href="#text-output-tab") Text
|
||||
#qr-code-tab.col.s12
|
||||
p.flow-text Scan QR Code
|
||||
a.copy-to-clipboard(data-clipboard-text=challenge)
|
||||
img.responsive-img(src=qrCode, alt="")
|
||||
#text-output-tab.col.s12
|
||||
p.flow-text
|
||||
small Please sign
|
||||
br
|
||||
span(style="word-wrap:anywhere")= challenge.message
|
||||
br
|
||||
small with the private key from the derivation path
|
||||
br
|
||||
span(style="word-wrap:anywhere")= challenge.derivationPath
|
||||
br
|
||||
small belonging to
|
||||
br
|
||||
span(style="word-wrap:anywhere")= challenge.xpub
|
||||
|
||||
a.btn-flat.copy-to-clipboard(data-clipboard-text=challenge) copy challenge to clipboard
|
||||
form(action="/account/register/xpub/response" method="POST")
|
||||
input(type="hidden", name="displayName", value=xpubUser.displayName)
|
||||
input(type="hidden", name="xpub", value=challenge.xpub)
|
||||
input(type="hidden", name="message", value=challenge.message)
|
||||
input(type="hidden", name="derivationPath", value=challenge.derivationPath)
|
||||
|
||||
input(type="hidden", name="request[signature]", value=challenge.request.signature)
|
||||
input(type="hidden", name="request[signatureDerivationPath]", value=challenge.request.signatureDerivationPath)
|
||||
.row
|
||||
<label for="signature">Signature:</label>
|
||||
<input id="signature" type="text" name="response[signature]" value="#{signature}"/>
|
||||
.row
|
||||
<div class="col s12 center">
|
||||
//- <button class="btn waves-effect waves-light" v-on:click="attemptLogin">Log In</button>
|
||||
<button class="btn blue waves-effect waves-light" type="submit" name="action">Respond
|
||||
</button>
|
||||
</div>
|
||||
//- p.center
|
||||
a.center(href="/reset-password") forgot password
|
||||
.col.s12
|
||||
|
||||
//- block additionalScripts
|
||||
script
|
||||
include js/copy-to-clipboard.js
|
65
server/views/xpub-login.pug
Normal file
@ -0,0 +1,65 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
-
|
||||
if (data)
|
||||
var message = data.message
|
||||
data = data == null ? {} : data;
|
||||
|
||||
.container
|
||||
.row
|
||||
.col.s12
|
||||
.row
|
||||
.col.s12
|
||||
.center
|
||||
h2.flow-text Authentication Challenge
|
||||
|
||||
.row
|
||||
.col.s12
|
||||
ul.tabs
|
||||
li.tab.s6
|
||||
a(href="#qr-code-tab") QR Code
|
||||
li.tab.s6
|
||||
a(href="#text-output-tab") Text
|
||||
#qr-code-tab.col.s12
|
||||
p.flow-text Scan QR Code
|
||||
a.copy-to-clipboard(data-clipboard-text=challenge)
|
||||
img.responsive-img(src=qrCode, alt="")
|
||||
#text-output-tab.col.s12
|
||||
p.flow-text
|
||||
small Please sign
|
||||
br
|
||||
span(style="word-wrap:anywhere")= challenge.message
|
||||
br
|
||||
small with the private key from the derivation path
|
||||
br
|
||||
span(style="word-wrap:anywhere")= challenge.derivationPath
|
||||
br
|
||||
small belonging to
|
||||
br
|
||||
span(style="word-wrap:anywhere")= challenge.xpub
|
||||
|
||||
a.btn-flat.copy-to-clipboard(data-clipboard-text=challenge) copy challenge to clipboard
|
||||
form(action="/account/authenticate/response" method="POST")
|
||||
input(type="hidden", name="xpub", value=challenge.xpub)
|
||||
input(type="hidden", name="message", value=challenge.message)
|
||||
input(type="hidden", name="derivationPath", value=challenge.derivationPath)
|
||||
|
||||
input(type="hidden", name="request[signature]", value=challenge.request.signature)
|
||||
input(type="hidden", name="request[signatureDerivationPath]", value=challenge.request.signatureDerivationPath)
|
||||
.row
|
||||
<label for="signature">Signature:</label>
|
||||
<input id="signature" type="text" name="response[signature]" value="#{signature}"/>
|
||||
.row
|
||||
<div class="col s12 center">
|
||||
//- <button class="btn waves-effect waves-light" v-on:click="attemptLogin">Log In</button>
|
||||
<button class="btn blue waves-effect waves-light" type="submit" name="action">Respond
|
||||
</button>
|
||||
</div>
|
||||
//- p.center
|
||||
a.center(href="/reset-password") forgot password
|
||||
.col.s12
|
||||
|
||||
//- block additionalScripts
|
||||
script
|
||||
include js/copy-to-clipboard.js
|