Genesis commit

This commit is contained in:
Kgothatso 2019-11-23 23:08:03 +02:00
parent 479b59cf51
commit a590b8ff3f
52 changed files with 15901 additions and 0 deletions

35
config/default.json Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

32
package.json Normal file
View 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"
}
}

View 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;
};

View 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;
};

View 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;
};

View 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;
};

View 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
View 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;
};

View 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
View 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
View 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;
};

View 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

File diff suppressed because one or more lines are too long

View 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;
}
}

View File

9
server/views/error.pug Normal file
View 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
View File

@ -0,0 +1,6 @@
extend templates/layout.pug
block content
.container
//- .row
h1 Welcome to the World

View 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

View 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
View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because one or more lines are too long

View 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

File diff suppressed because one or more lines are too long

12374
server/views/js/materialize.js vendored Normal file

File diff suppressed because it is too large Load Diff

6
server/views/js/materialize.min.js vendored Normal file

File diff suppressed because one or more lines are too long

9
server/views/js/site.js Normal file
View File

@ -0,0 +1,9 @@
// SITE WIDE Javascript...
$(document).ready(function() {
$('.sidenav').sidenav();
$('.tabs').tabs({
onShow: function () {
// TODO: remove input values from other tabs...
}
});
})

View 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

View 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

View 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 &copy; 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

View 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

View 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