commit latest changes in prep for crystal rewrite
This commit is contained in:
parent
2b0a5ddaf9
commit
4d32a3e146
14 changed files with 408 additions and 184 deletions
|
@ -3,11 +3,14 @@
|
||||||
"https": true,
|
"https": true,
|
||||||
"alter_db": true,
|
"alter_db": true,
|
||||||
"port": 8080,
|
"port": 8080,
|
||||||
|
"frontend_url": "localhost:3000",
|
||||||
"db_url": "postgres://postgres:@127.0.0.1/todo",
|
"db_url": "postgres://postgres:@127.0.0.1/todo",
|
||||||
"cert": "",
|
"cert": "",
|
||||||
"cert_key": "",
|
"cert_key": "",
|
||||||
"mail_host": "",
|
"mail_host": "",
|
||||||
"mail_port": 465,
|
"mail_port": 465,
|
||||||
"mail_username": "",
|
"mail_username": "",
|
||||||
"mail_password": ""
|
"mail_password": "",
|
||||||
|
"discord_id": "",
|
||||||
|
"discord_secret": ""
|
||||||
}
|
}
|
|
@ -5,7 +5,8 @@
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"who": "pwd",
|
"who": "pwd",
|
||||||
"start": "node src/index.js"
|
"start": "node src/index.js",
|
||||||
|
"test": "mocha"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -18,11 +19,18 @@
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-paginate": "^1.0.2",
|
"express-paginate": "^1.0.2",
|
||||||
"nodemailer": "^6.6.1",
|
"http-proxy": "^1.18.1",
|
||||||
|
"node-fetch": "^2.6.1",
|
||||||
|
"nodemailer": "^6.6.2",
|
||||||
"pg": "^8.6.0",
|
"pg": "^8.6.0",
|
||||||
"sequelize": "^6.6.2"
|
"sequelize": "^6.6.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"sequelize-cli": "^6.2.0"
|
"chai": "^4.3.4",
|
||||||
|
"mocha": "^9.0.2",
|
||||||
|
"proxyrequire": "^1.0.21",
|
||||||
|
"sequelize-cli": "^6.2.0",
|
||||||
|
"sequelize-test-helpers": "^1.3.3",
|
||||||
|
"sinon": "^11.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
const Sequelize = require('sequelize');
|
const Sequelize = require('sequelize');
|
||||||
const Config = require('./config.js');
|
const Config = require('./config.js');
|
||||||
|
const Models = require('./models');
|
||||||
|
|
||||||
if (!Config.config.db_url) {
|
if (!Config.config.db_url) {
|
||||||
console.error('No database url found. please set `db_url` in config.json');
|
console.error('No database url found. please set `db_url` in config.json');
|
||||||
|
@ -8,118 +9,6 @@ if (!Config.config.db_url) {
|
||||||
|
|
||||||
const db = new Sequelize(Config.config.db_url);
|
const db = new Sequelize(Config.config.db_url);
|
||||||
|
|
||||||
const UnverifiedUser = db.define('UnverifiedUser', {
|
|
||||||
id: {
|
|
||||||
type: Sequelize.DataTypes.UUID,
|
|
||||||
defaultValue: Sequelize.UUIDV4,
|
|
||||||
allowNull: false,
|
|
||||||
primaryKey: true,
|
|
||||||
unique: true
|
|
||||||
},
|
|
||||||
verificationToken: {
|
|
||||||
type: Sequelize.DataTypes.STRING,
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
type: Sequelize.DataTypes.STRING,
|
|
||||||
allowNull: false,
|
|
||||||
unique: true
|
|
||||||
},
|
|
||||||
password_hash: {
|
|
||||||
type: Sequelize.DataTypes.STRING,
|
|
||||||
allowNull: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const User = db.define('User', {
|
|
||||||
id: {
|
|
||||||
type: Sequelize.DataTypes.UUID,
|
|
||||||
defaultValue: Sequelize.UUIDV4,
|
|
||||||
allowNull: false,
|
|
||||||
primaryKey: true,
|
|
||||||
unique: true
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
type: Sequelize.DataTypes.STRING,
|
|
||||||
allowNull: false,
|
|
||||||
unique: true
|
|
||||||
},
|
|
||||||
password_hash: {
|
|
||||||
type: Sequelize.DataTypes.STRING,
|
|
||||||
allowNull: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const Todo = db.define('Todo', {
|
|
||||||
id: {
|
|
||||||
type: Sequelize.DataTypes.UUID,
|
|
||||||
defaultValue: Sequelize.UUIDV4,
|
|
||||||
allowNull: false,
|
|
||||||
primaryKey: true,
|
|
||||||
unique: true
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
type: Sequelize.DataTypes.UUID,
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
type: Sequelize.DataTypes.TEXT,
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
tags: {
|
|
||||||
type: Sequelize.DataTypes.ARRAY(Sequelize.DataTypes.STRING),
|
|
||||||
allowNull: true
|
|
||||||
},
|
|
||||||
complete: {
|
|
||||||
type: Sequelize.DataTypes.BOOLEAN,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: false
|
|
||||||
},
|
|
||||||
deadline: {
|
|
||||||
type: Sequelize.DataTypes.DATE,
|
|
||||||
allowNull: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const Grouping = db.define('Grouping', {
|
|
||||||
id: {
|
|
||||||
type: Sequelize.DataTypes.UUID,
|
|
||||||
defaultValue: Sequelize.UUIDV4,
|
|
||||||
allowNull: false,
|
|
||||||
primaryKey: true,
|
|
||||||
unique: true
|
|
||||||
},
|
|
||||||
complete: {
|
|
||||||
type: Sequelize.DataTypes.BOOLEAN,
|
|
||||||
allowNull: true
|
|
||||||
},
|
|
||||||
manually_added: {
|
|
||||||
type: Sequelize.DataTypes.ARRAY(Sequelize.DataTypes.UUID),
|
|
||||||
allowNull: true
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
type: Sequelize.DataTypes.ARRAY(Sequelize.DataTypes.STRING),
|
|
||||||
allowNull: true
|
|
||||||
},
|
|
||||||
exclusions: {
|
|
||||||
type: Sequelize.DataTypes.ARRAY(Sequelize.DataTypes.STRING),
|
|
||||||
allowNull: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
alter: false
|
|
||||||
};
|
|
||||||
if (Config.config.alter_db) {
|
|
||||||
options.alter = true;
|
|
||||||
}
|
|
||||||
let start = async () => {
|
|
||||||
await UnverifiedUser.sync(options);
|
|
||||||
await User.sync(options);
|
|
||||||
await Todo.sync(options);
|
|
||||||
await Grouping.sync(options);
|
|
||||||
};
|
|
||||||
start();
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
db: db,
|
db: db,
|
||||||
constructors: {
|
constructors: {
|
||||||
|
@ -127,10 +16,5 @@ module.exports = {
|
||||||
return User.build();
|
return User.build();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
schemas: {
|
schemas: Models(db, Sequelize)
|
||||||
user: User,
|
|
||||||
unverifiedUser: UnverifiedUser,
|
|
||||||
todo: Todo,
|
|
||||||
grouping: Grouping
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
|
const httpProxy = require('http-proxy');
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
|
const fs = require('fs');
|
||||||
const Config = require('./config.js');
|
const Config = require('./config.js');
|
||||||
|
|
||||||
const UserInterface = require('./user.js');
|
const UserInterface = require('./user.js');
|
||||||
|
@ -15,6 +17,10 @@ if (Config.config.https) {
|
||||||
credentials.key = fs.readFileSync(Config.config.cert_key);
|
credentials.key = fs.readFileSync(Config.config.cert_key);
|
||||||
credentials.cert = fs.readFileSync(Config.config.cert);
|
credentials.cert = fs.readFileSync(Config.config.cert);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
console.error('could not load certs')
|
||||||
|
process.exit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let app = express();
|
let app = express();
|
||||||
|
@ -43,10 +49,14 @@ if (!Config.config.secret) {
|
||||||
app.use('/api/user', UserInterface.router);
|
app.use('/api/user', UserInterface.router);
|
||||||
app.use('/api/todo', TodoInterface.router);
|
app.use('/api/todo', TodoInterface.router);
|
||||||
|
|
||||||
// serve static files last
|
if (Config.config.frontend_url) {
|
||||||
// app.use(express.static('./static'));
|
const proxy = httpProxy.createProxyServer({})
|
||||||
// DISABLED: no longer needs to serve static files
|
app.use('/', (req, res) => {
|
||||||
// due to frontend being employed in elm
|
return proxy.web(req, res, {
|
||||||
|
target: Config.config.frontend_url
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (Config.config.https) {
|
if (Config.config.https) {
|
||||||
var server = https.createServer(credentials, app);
|
var server = https.createServer(credentials, app);
|
||||||
|
|
141
backend/src/models.js
Normal file
141
backend/src/models.js
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
const Config = require('./config.js');
|
||||||
|
|
||||||
|
const models = (db) => {
|
||||||
|
const UnverifiedUser = db.define('UnverifiedUser', {
|
||||||
|
id: {
|
||||||
|
type: Sequelize.DataTypes.UUID,
|
||||||
|
defaultValue: Sequelize.UUIDV4,
|
||||||
|
allowNull: false,
|
||||||
|
primaryKey: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
verificationToken: {
|
||||||
|
type: Sequelize.DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: Sequelize.DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
discord_only_account: {
|
||||||
|
type: Sequelize.DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
discord_id: {
|
||||||
|
type: Sequelize.DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
password_hash: {
|
||||||
|
type: Sequelize.DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const User = db.define('User', {
|
||||||
|
id: {
|
||||||
|
type: Sequelize.DataTypes.UUID,
|
||||||
|
defaultValue: Sequelize.UUIDV4,
|
||||||
|
allowNull: false,
|
||||||
|
primaryKey: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: Sequelize.DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
discord_only_account: {
|
||||||
|
type: Sequelize.DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
discord_id: {
|
||||||
|
type: Sequelize.DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
password_hash: {
|
||||||
|
type: Sequelize.DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Todo = db.define('Todo', {
|
||||||
|
id: {
|
||||||
|
type: Sequelize.DataTypes.UUID,
|
||||||
|
defaultValue: Sequelize.UUIDV4,
|
||||||
|
allowNull: false,
|
||||||
|
primaryKey: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: Sequelize.DataTypes.UUID,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
type: Sequelize.DataTypes.TEXT,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
type: Sequelize.DataTypes.ARRAY(Sequelize.DataTypes.STRING),
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
complete: {
|
||||||
|
type: Sequelize.DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
deadline: {
|
||||||
|
type: Sequelize.DataTypes.DATE,
|
||||||
|
allowNull: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Grouping = db.define('Grouping', {
|
||||||
|
id: {
|
||||||
|
type: Sequelize.DataTypes.UUID,
|
||||||
|
defaultValue: Sequelize.UUIDV4,
|
||||||
|
allowNull: false,
|
||||||
|
primaryKey: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
complete: {
|
||||||
|
type: Sequelize.DataTypes.BOOLEAN,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
manually_added: {
|
||||||
|
type: Sequelize.DataTypes.ARRAY(Sequelize.DataTypes.UUID),
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
type: Sequelize.DataTypes.ARRAY(Sequelize.DataTypes.STRING),
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
exclusions: {
|
||||||
|
type: Sequelize.DataTypes.ARRAY(Sequelize.DataTypes.STRING),
|
||||||
|
allowNull: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
alter: false
|
||||||
|
};
|
||||||
|
if (Config.config.alter_db) {
|
||||||
|
options.alter = true;
|
||||||
|
}
|
||||||
|
UnverifiedUser.sync(options);
|
||||||
|
User.sync(options);
|
||||||
|
Todo.sync(options);
|
||||||
|
Grouping.sync(options);
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: User,
|
||||||
|
unverifiedUser: UnverifiedUser,
|
||||||
|
todo: Todo,
|
||||||
|
grouping: Grouping
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = models;
|
|
@ -1,5 +1,6 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
const fetch = require('node-fetch');
|
||||||
const Config = require('./config.js');
|
const Config = require('./config.js');
|
||||||
const Database = require('./db_interface.js');
|
const Database = require('./db_interface.js');
|
||||||
const Mail = require('./mail.js');
|
const Mail = require('./mail.js');
|
||||||
|
@ -12,6 +13,86 @@ let session_entropy = {};
|
||||||
|
|
||||||
user_cache = {};
|
user_cache = {};
|
||||||
email_cache = {};
|
email_cache = {};
|
||||||
|
discord_cache = {};
|
||||||
|
discord_user_cache = {};
|
||||||
|
|
||||||
|
async function fetch_user(where) {
|
||||||
|
let user = await Database.schemas.user.findOne({ where: where });
|
||||||
|
if (user === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
user_cache[user.id] = {
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
discord_id: user.discord_id,
|
||||||
|
password_hash: user.password_hash
|
||||||
|
};
|
||||||
|
email_cache[user.email] = user.id;
|
||||||
|
if (user.discord_id) {
|
||||||
|
discord_cache[user.discord_id] = user.id
|
||||||
|
}
|
||||||
|
return user_cache[user.id]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetch_discord_user(auth) {
|
||||||
|
const result = await fetch(`https://discord.com/api/v8/users/@me`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': auth.token_type + ' ' + auth.access_token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const json = result.json();
|
||||||
|
discord_user_cache[json.id] = {
|
||||||
|
user: json,
|
||||||
|
auth: auth,
|
||||||
|
expires: (new Date().getTime()) + (json.expires_in * 1000)
|
||||||
|
}
|
||||||
|
return discord_user_cache[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function acquire_discord_token(code, redirect) {
|
||||||
|
let data = {
|
||||||
|
client_id: Config.config.discord_id,
|
||||||
|
client_secret: Config.config.discord_secret,
|
||||||
|
grant_type: 'authorization_code',
|
||||||
|
code: code,
|
||||||
|
redirect_uri: redirect
|
||||||
|
}
|
||||||
|
const result = await fetch(`https://discord.com/api/oauth2/token`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
}
|
||||||
|
}).catch(err => console.error(err));
|
||||||
|
if (!result.ok) {
|
||||||
|
return res.status(500).json({error: "could not fetch user details"})
|
||||||
|
}
|
||||||
|
const json = result.json();
|
||||||
|
return fetch_discord_user(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refresh_discord_token(id) {
|
||||||
|
let data = {
|
||||||
|
client_id: Config.config.discord_id,
|
||||||
|
client_secret: Config.config.discord_secret,
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
refresh_token: discord_user_cache[id].auth.refresh_token
|
||||||
|
}
|
||||||
|
const result = await fetch(`https://discord.com/api/oauth2/token`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
}
|
||||||
|
}).catch(err => console.error(err));
|
||||||
|
if (!result.ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const json = result.json();
|
||||||
|
discord_user_cache[id].auth.access_token = json.access_token;
|
||||||
|
discord_user_cache[id].expires = (new Date().getTime()) + (json.expires_in * 1000);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
async function get_user_details(id) {
|
async function get_user_details(id) {
|
||||||
if (!id || id === 'undefined') {
|
if (!id || id === 'undefined') {
|
||||||
|
@ -19,18 +100,9 @@ async function get_user_details(id) {
|
||||||
}
|
}
|
||||||
console.log(`search for user with id ${id}`);
|
console.log(`search for user with id ${id}`);
|
||||||
if (!user_cache[id]) {
|
if (!user_cache[id]) {
|
||||||
let user = await Database.schemas.user.findOne({ where: { id: id } });
|
return await fetch_user({ id: id })
|
||||||
if (user === null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
user_cache[user.id] = {
|
|
||||||
id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
password_hash: user.password_hash
|
|
||||||
};
|
|
||||||
email_cache[user.email] = user.id;
|
|
||||||
}
|
}
|
||||||
console.log(`returning ${JSON.stringify(user_cache[id])}`);
|
// console.log(`returning ${JSON.stringify(user_cache[id])}`);
|
||||||
return user_cache[id];
|
return user_cache[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,38 +112,21 @@ async function get_user_details_by_email(email) {
|
||||||
}
|
}
|
||||||
console.log(`search for user with email ${email}}`);
|
console.log(`search for user with email ${email}}`);
|
||||||
if (!email_cache[email] || !user_cache[email_cache[email]]) {
|
if (!email_cache[email] || !user_cache[email_cache[email]]) {
|
||||||
let user = await Database.schemas.user.findOne({ where: { email: email } });
|
return await fetch_user({ email: email })
|
||||||
if (user === null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
user_cache[user.id] = {
|
|
||||||
id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
password_hash: user.password_hash
|
|
||||||
};
|
|
||||||
email_cache[user.email] = user.id;
|
|
||||||
}
|
}
|
||||||
console.log(`returning ${JSON.stringify(user_cache[email_cache[email]])}`);
|
// console.log(`returning ${JSON.stringify(user_cache[email_cache[email]])}`);
|
||||||
return user_cache[email_cache[email]];
|
return user_cache[email_cache[email]];
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get('/byEmail/:email', async (req, res) => {
|
async function get_user_details_by_discord_id(id) {
|
||||||
if (!req.params?.email) {
|
if (!id || id === 'undefined') {
|
||||||
res.status(400).json({
|
return undefined;
|
||||||
error: 'email is a required parameter'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
let user = get_user_details_by_email(req.params.email);
|
if (!discord_cache[id] || !user_cache[discord_cache[id]]) {
|
||||||
console.log(user);
|
return await fetch_user({ discord_id: id })
|
||||||
if (user !== undefined && user !== {}) {
|
|
||||||
res.json({
|
|
||||||
id: user.id,
|
|
||||||
email: user.email
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.sendStatus(404);
|
|
||||||
}
|
}
|
||||||
});
|
return user_cache[discord_cache[id]];
|
||||||
|
}
|
||||||
|
|
||||||
function hash(secret, password, base64 = true) {
|
function hash(secret, password, base64 = true) {
|
||||||
let pw_hash = crypto.pbkdf2Sync(
|
let pw_hash = crypto.pbkdf2Sync(
|
||||||
|
@ -164,9 +219,8 @@ router.post('/signup', async (req, res) => {
|
||||||
password_hash: password_hash,
|
password_hash: password_hash,
|
||||||
verificationToken: get_session_token(randomString, password_hash, false)
|
verificationToken: get_session_token(randomString, password_hash, false)
|
||||||
});
|
});
|
||||||
const link = `${Config.config.https ? 'https://' : 'http://'}${req.headers.host}/api/user/verify?verification=${
|
const link = `${Config.config.https ? 'https://' : 'http://'}${req.headers.host}/api/user/verify?verification=${user.verificationToken
|
||||||
user.verificationToken
|
}`;
|
||||||
}`;
|
|
||||||
const content = `Click here to verify your sign-up:
|
const content = `Click here to verify your sign-up:
|
||||||
${link}`;
|
${link}`;
|
||||||
const contentHtml = `<h1>Click here to verify your sign-up:</h1>
|
const contentHtml = `<h1>Click here to verify your sign-up:</h1>
|
||||||
|
@ -218,6 +272,45 @@ router.get('/verify', async (req, res) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get('/login/discord', async (req, res) => {
|
||||||
|
if (!Config.config.discord_id || !Config.config.discord_secret) {
|
||||||
|
return res.status(403).send("discord login is not enabled.");
|
||||||
|
}
|
||||||
|
const url = encodeURIComponent(`${req.headers.host}discord`);
|
||||||
|
return res.send(`https://discord.com/api/oauth2/authorize?client_id=${Config.config.discord_id}&redirect_uri=${url}&response_type=code&scope=identify%20email%20guilds`);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/login/discord', async (req, res) => {
|
||||||
|
if (!Config.config.discord_id || !Config.config.discord_secret) {
|
||||||
|
return res.status(403).json({ error: "discord login is not enabled." });
|
||||||
|
}
|
||||||
|
if (!req.params.code || !req.headers.host) {
|
||||||
|
return res.status(400).json({error: "invalid oauth request"});
|
||||||
|
}
|
||||||
|
const result = await acquire_discord_token(req.params.code, req.headers.host);
|
||||||
|
const matching_account = await get_user_details_by_discord_id(result.user.id);
|
||||||
|
if (!matching_account) {
|
||||||
|
let user = await Database.schemas.unverifiedUser.create({
|
||||||
|
email: String(result.user.email),
|
||||||
|
discord_id: user.id,
|
||||||
|
verificationToken: get_session_token(randomString, result.auth.access_token, false)
|
||||||
|
});
|
||||||
|
return res.json({
|
||||||
|
type: 'unverified',
|
||||||
|
verificationToken: user.verificationToken
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res.json({
|
||||||
|
type: 'verified',
|
||||||
|
userid: matching_account.id,
|
||||||
|
session_token: get_session_token(matching_account.id, result.auth.access_token)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
router.post('/discord/create', async (req, res) =>{});
|
||||||
|
router.post('/discord/link', async (req, res) =>{});
|
||||||
|
|
||||||
router.post('/login', async (req, res) => {
|
router.post('/login', async (req, res) => {
|
||||||
if (!req.body?.email || !req.body?.password) {
|
if (!req.body?.email || !req.body?.password) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
|
@ -244,15 +337,11 @@ router.post('/login', async (req, res) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/logout', async (req, res) => {
|
|
||||||
if (!req.body?.session_token || !req.body?.userid) {
|
|
||||||
return res.status(400).json({
|
|
||||||
error: 'must include user id and session token to log out'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let userid = req.body?.userid;
|
router.use('/logout', enforce_session_login)
|
||||||
let session_token = req.body?.session_token;
|
router.post('/logout', async (req, res) => {
|
||||||
|
let userid = req.get('id');
|
||||||
|
let session_token = req.get('authorization');
|
||||||
|
|
||||||
let user = await get_user_details(userid);
|
let user = await get_user_details(userid);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
@ -272,6 +361,27 @@ router.post('/logout', async (req, res) => {
|
||||||
return res.sendStatus(204);
|
return res.sendStatus(204);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.use('/byEmail', enforce_session_login)
|
||||||
|
router.get('/byEmail/:email', async (req, res) => {
|
||||||
|
if (!req.params?.email) {
|
||||||
|
res.status(400).json({
|
||||||
|
error: 'email is a required parameter'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let user = get_user_details_by_email(req.params.email);
|
||||||
|
console.log(user);
|
||||||
|
if (user !== undefined && user !== {}) {
|
||||||
|
res.json({
|
||||||
|
id: user.id,
|
||||||
|
email: user.email
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.sendStatus(404);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
router.use('/', enforce_session_login)
|
||||||
router.get('/:id([a-f0-9-]+)', async (req, res) => {
|
router.get('/:id([a-f0-9-]+)', async (req, res) => {
|
||||||
console.log(req.params);
|
console.log(req.params);
|
||||||
if (!req.params?.id) {
|
if (!req.params?.id) {
|
||||||
|
|
17
backend/test/db_interface.spec.js
Normal file
17
backend/test/db_interface.spec.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
const { expect } = require('chai');
|
||||||
|
const Models = require('../src/models');
|
||||||
|
|
||||||
|
const { sequelize, checkModelName, checkUniqueIndex, checkPropertyExists } = require('sequelize-test-helpers');
|
||||||
|
|
||||||
|
describe('Sequelize model tests', function () {
|
||||||
|
const models = Models(sequelize);
|
||||||
|
|
||||||
|
checkModelName(models.user)('User');
|
||||||
|
checkModelName(models.unverifiedUser)('UnverifiedUser');
|
||||||
|
checkModelName(models.grouping)('Grouping');
|
||||||
|
checkModelName(models.todo)('Todo');
|
||||||
|
|
||||||
|
context('user props', function () {
|
||||||
|
['id', 'email', 'discord_only_account'].forEach(checkPropertyExists(new models.user()));
|
||||||
|
});
|
||||||
|
});
|
10
backend/test/user.spec.js
Normal file
10
backend/test/user.spec.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
const { expect } = require('chai');
|
||||||
|
const proxyrequire = require('proxyrequire');
|
||||||
|
const { match, stub, resetHistory } = require('sinon');
|
||||||
|
const { sequelize, Sequelize, makeMockModels } = require('sequelize-test-helpers');
|
||||||
|
|
||||||
|
describe('User Router Tests', function () {
|
||||||
|
const Database = proxyrequire('../src/db_interface', {
|
||||||
|
sequelize: Sequelize
|
||||||
|
});
|
||||||
|
});
|
|
@ -5,13 +5,13 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.4.0",
|
"@emotion/react": "^11.4.0",
|
||||||
"@emotion/styled": "^11.3.0",
|
"@emotion/styled": "^11.3.0",
|
||||||
"@material-ui/core": "^5.0.0-alpha.36",
|
"@material-ui/core": "^5.0.0-beta.0",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/styles": "^4.11.4",
|
"@material-ui/styles": "^4.11.4",
|
||||||
"@reduxjs/toolkit": "^1.6.0",
|
"@reduxjs/toolkit": "^1.6.0",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.2.7",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.8.3",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"web-vitals": "^1.0.1"
|
"web-vitals": "^1.1.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
{
|
{
|
||||||
"hosts": {
|
"hosts": {
|
||||||
"localhost:3000": "LOCAL",
|
"localhost:3000": "LOCAL",
|
||||||
|
"dev.j4.pm": "test",
|
||||||
"todo.j4.pm": "prod"
|
"todo.j4.pm": "prod"
|
||||||
},
|
},
|
||||||
"defaultConfig": {},
|
"defaultConfig": {},
|
||||||
"configs": {
|
"configs": {
|
||||||
"LOCAL": {},
|
"LOCAL": {},
|
||||||
"prod": {}
|
"test": {"apiUrl": "https://dev.j4.pm/api"},
|
||||||
|
"prod": {"apiUrl": "https://todo.j4.pm/api"}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@ import RootPage from './modules/Root';
|
||||||
import AboutPage from './modules/About';
|
import AboutPage from './modules/About';
|
||||||
import AccountPage from './modules/Account';
|
import AccountPage from './modules/Account';
|
||||||
import LoginPage from './modules/Login';
|
import LoginPage from './modules/Login';
|
||||||
|
import OauthPage from './modules/Oauth';
|
||||||
import SignupPage from './modules/Signup';
|
import SignupPage from './modules/Signup';
|
||||||
import TodoPage from './modules/TodoList';
|
import TodoPage from './modules/TodoList';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
@ -24,15 +25,25 @@ const App = (props) => {
|
||||||
<Route path='/signup'>
|
<Route path='/signup'>
|
||||||
<SignupPage />
|
<SignupPage />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path='/discord'>
|
||||||
|
<OauthPage />
|
||||||
|
</Route>
|
||||||
<PRoute path='/todos'>
|
<PRoute path='/todos'>
|
||||||
<TodoPage />
|
<TodoPage />
|
||||||
</PRoute>
|
</PRoute>
|
||||||
<PRoute path='/account'>
|
<PRoute path='/account'>
|
||||||
<AccountPage />
|
<AccountPage />
|
||||||
</PRoute>
|
</PRoute>
|
||||||
<PRoute path='/'>
|
<PRoute exact path='/'>
|
||||||
<RootPage />
|
<RootPage />
|
||||||
</PRoute>
|
</PRoute>
|
||||||
|
<Route path='/'>
|
||||||
|
<Redirect
|
||||||
|
to={{
|
||||||
|
pathname: '/'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|
|
@ -11,7 +11,7 @@ import VisibilityOff from '@material-ui/icons/VisibilityOff';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { withStyles } from '@material-ui/styles';
|
import { withStyles } from '@material-ui/styles';
|
||||||
|
|
||||||
const styles = (theme) => {};
|
const styles = (theme) => { };
|
||||||
|
|
||||||
const LoginPage = (props) => {
|
const LoginPage = (props) => {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
|
@ -137,6 +137,17 @@ const LoginPage = (props) => {
|
||||||
Continue
|
Continue
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Button
|
||||||
|
id='login-with-discord'
|
||||||
|
variant='contained'
|
||||||
|
fullWidth
|
||||||
|
color='secondary'
|
||||||
|
onClick={()=>{
|
||||||
|
|
||||||
|
}}
|
||||||
|
>Sign in with Discord</Button>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
17
frontend/src/modules/Oauth/index.js
Normal file
17
frontend/src/modules/Oauth/index.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { withStyles } from '@material-ui/styles';
|
||||||
|
|
||||||
|
const styles = (theme) => { };
|
||||||
|
|
||||||
|
const OauthPage = (props) => {
|
||||||
|
return <div>test</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
(state) => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
(dispatch, props) => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
)(withStyles(styles)(OauthPage));
|
|
@ -14,7 +14,7 @@ import { withStyles } from '@material-ui/styles';
|
||||||
const styles = (theme) => {};
|
const styles = (theme) => {};
|
||||||
|
|
||||||
const SignupPage = (props) => {
|
const SignupPage = (props) => {
|
||||||
const { classes } = props;
|
//const { classes } = props;
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [confirmPassword, setConfirmPassword] = useState('');
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
|
|
Loading…
Reference in a new issue