begin impl of todo endpoints
This commit is contained in:
parent
093a679b6d
commit
4c58da37f4
12 changed files with 1438 additions and 492 deletions
16
backend/.prettierrc.yaml
Normal file
16
backend/.prettierrc.yaml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
arrowParens: 'always'
|
||||||
|
bracketSpacing: true
|
||||||
|
endOfLine: 'lf'
|
||||||
|
htmlWhitespaceSensitivity: 'css'
|
||||||
|
insertPragma: false
|
||||||
|
jsxBracketSameLine: true
|
||||||
|
jsxSingleQuote: true
|
||||||
|
printWidth: 120
|
||||||
|
proseWrap: 'preserve'
|
||||||
|
quoteProps: 'consistent'
|
||||||
|
requirePragma: false
|
||||||
|
semi: true
|
||||||
|
singleQuote: true
|
||||||
|
tabWidth: 2
|
||||||
|
trailingComma: 'none'
|
||||||
|
useTabs: false
|
|
@ -6,6 +6,8 @@
|
||||||
"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_port": 465,
|
||||||
"mail_username": "",
|
"mail_username": "",
|
||||||
"mail_password": ""
|
"mail_password": ""
|
||||||
}
|
}
|
|
@ -17,8 +17,12 @@
|
||||||
"cookie-parser": "^1.4.5",
|
"cookie-parser": "^1.4.5",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"express-paginate": "^1.0.2",
|
||||||
"nodemailer": "^6.6.1",
|
"nodemailer": "^6.6.1",
|
||||||
"pg": "^8.6.0",
|
"pg": "^8.6.0",
|
||||||
"sequelize": "^6.6.2"
|
"sequelize": "^6.6.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"sequelize-cli": "^6.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,105 +1,136 @@
|
||||||
const Sequelize = require("sequelize");
|
const Sequelize = require('sequelize');
|
||||||
const Config = require("./config.js");
|
const Config = require('./config.js');
|
||||||
|
|
||||||
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');
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = new Sequelize(Config.config.db_url);
|
const db = new Sequelize(Config.config.db_url);
|
||||||
|
|
||||||
const UnverifiedUser = db.define("UnverifiedUser", {
|
const UnverifiedUser = db.define('UnverifiedUser', {
|
||||||
id: {
|
id: {
|
||||||
type: Sequelize.DataTypes.UUID,
|
type: Sequelize.DataTypes.UUID,
|
||||||
defaultValue: Sequelize.UUIDV4,
|
defaultValue: Sequelize.UUIDV4,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
unique: true,
|
unique: true
|
||||||
},
|
},
|
||||||
verificationToken: {
|
verificationToken: {
|
||||||
type: Sequelize.DataTypes.STRING,
|
type: Sequelize.DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: false
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
type: Sequelize.DataTypes.STRING,
|
type: Sequelize.DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
unique: true,
|
unique: true
|
||||||
},
|
},
|
||||||
password_hash: {
|
password_hash: {
|
||||||
type: Sequelize.DataTypes.STRING,
|
type: Sequelize.DataTypes.STRING,
|
||||||
allowNull: true,
|
allowNull: true
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const User = db.define("User", {
|
const User = db.define('User', {
|
||||||
id: {
|
id: {
|
||||||
type: Sequelize.DataTypes.UUID,
|
type: Sequelize.DataTypes.UUID,
|
||||||
defaultValue: Sequelize.UUIDV4,
|
defaultValue: Sequelize.UUIDV4,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
unique: true,
|
unique: true
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
type: Sequelize.DataTypes.STRING,
|
type: Sequelize.DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
unique: true,
|
unique: true
|
||||||
},
|
},
|
||||||
password_hash: {
|
password_hash: {
|
||||||
type: Sequelize.DataTypes.STRING,
|
type: Sequelize.DataTypes.STRING,
|
||||||
allowNull: true,
|
allowNull: true
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const Todo = db.define("Todo", {
|
const Todo = db.define('Todo', {
|
||||||
id: {
|
id: {
|
||||||
type: Sequelize.DataTypes.UUID,
|
type: Sequelize.DataTypes.UUID,
|
||||||
defaultValue: Sequelize.UUIDV4,
|
defaultValue: Sequelize.UUIDV4,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
unique: true,
|
unique: true
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: Sequelize.DataTypes.UUID,
|
||||||
|
allowNull: false
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
type: Sequelize.DataTypes.TEXT,
|
type: Sequelize.DataTypes.TEXT,
|
||||||
allowNull: false,
|
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 Tag = db.define("Tag", {
|
const Grouping = db.define('Grouping', {
|
||||||
id: {
|
id: {
|
||||||
type: Sequelize.DataTypes.UUID,
|
type: Sequelize.DataTypes.UUID,
|
||||||
defaultValue: Sequelize.UUIDV4,
|
defaultValue: Sequelize.UUIDV4,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
unique: true,
|
unique: true
|
||||||
},
|
},
|
||||||
content: {
|
complete: {
|
||||||
type: Sequelize.DataTypes.STRING,
|
type: Sequelize.DataTypes.BOOLEAN,
|
||||||
allowNull: false,
|
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
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
User.hasMany(Todo);
|
|
||||||
Todo.hasMany(Tag);
|
|
||||||
|
|
||||||
let options = {
|
let options = {
|
||||||
alter: false,
|
alter: false
|
||||||
};
|
};
|
||||||
if (Config.config.alter_db) {
|
if (Config.config.alter_db) {
|
||||||
options.alter = true;
|
options.alter = true;
|
||||||
}
|
}
|
||||||
|
let start = async () => {
|
||||||
UnverifiedUser.sync(options);
|
await UnverifiedUser.sync(options);
|
||||||
User.sync(options);
|
await User.sync(options);
|
||||||
Todo.sync(options);
|
await Todo.sync(options);
|
||||||
Tag.sync(options);
|
await Grouping.sync(options);
|
||||||
|
};
|
||||||
|
start();
|
||||||
module.exports = {
|
module.exports = {
|
||||||
db: db,
|
db: db,
|
||||||
constructors: {
|
constructors: {
|
||||||
user: () => {
|
user: () => {
|
||||||
return User.build();
|
return User.build();
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
schemas: {
|
schemas: {
|
||||||
user: User,
|
user: User,
|
||||||
},
|
unverifiedUser: UnverifiedUser,
|
||||||
|
todo: Todo,
|
||||||
|
grouping: Grouping
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
const http = require("http");
|
const http = require('http');
|
||||||
const https = require("https");
|
const https = require('https');
|
||||||
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 Config = require("./config.js");
|
const Config = require('./config.js');
|
||||||
|
|
||||||
const UserInterface = require("./user.js");
|
const UserInterface = require('./user.js');
|
||||||
|
const TodoInterface = require('./todo.js');
|
||||||
|
|
||||||
let credentials = {};
|
let credentials = {};
|
||||||
|
|
||||||
if (Config.config.https) {
|
if (Config.config.https) {
|
||||||
if (
|
if (fs.existsSync(Config.config.cert) && fs.existsSync(Config.config.cert_key)) {
|
||||||
fs.existsSync(Config.config.cert) &&
|
|
||||||
fs.existsSync(Config.config.cert_key)
|
|
||||||
) {
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +25,7 @@ app.use(cookieParser());
|
||||||
// force https
|
// force https
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
if (Config.config.https) {
|
if (Config.config.https) {
|
||||||
if (req.headers["x-forwarded-proto"] !== "https") {
|
if (req.headers['x-forwarded-proto'] !== 'https') {
|
||||||
return res.redirect(`https://${req.headers.host}${req.url}`);
|
return res.redirect(`https://${req.headers.host}${req.url}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,14 +33,15 @@ app.use((req, res, next) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!Config.config.secret) {
|
if (!Config.config.secret) {
|
||||||
console.error("No password secret found. please set `secret` in config.json");
|
console.error('No password secret found. please set `secret` in config.json');
|
||||||
process.exit();
|
process.exit();
|
||||||
} else if (Config.config.https && Config.config.secret == "TEST_SECRET") {
|
} else if (Config.config.https && Config.config.secret == 'TEST_SECRET') {
|
||||||
console.error("please do not use the testing secret in production.");
|
console.error('please do not use the testing secret in production.');
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use("/api/user", UserInterface.router);
|
app.use('/api/user', UserInterface.router);
|
||||||
|
app.use('/api/todo', TodoInterface.router);
|
||||||
|
|
||||||
// serve static files last
|
// serve static files last
|
||||||
// app.use(express.static('./static'));
|
// app.use(express.static('./static'));
|
||||||
|
@ -57,6 +56,5 @@ if (Config.config.https) {
|
||||||
server.listen(Config.config.port || 8080);
|
server.listen(Config.config.port || 8080);
|
||||||
}
|
}
|
||||||
console.log(
|
console.log(
|
||||||
`listening on port ${Config.config.port || 8080}` +
|
`listening on port ${Config.config.port || 8080}` + ` with https ${Config.config.https ? 'enabled' : 'disabled'}`
|
||||||
` with https ${Config.config.https ? "enabled" : "disabled"}`
|
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,3 +1,52 @@
|
||||||
|
const Config = require('./config.js');
|
||||||
const nodemailer = require('nodemailer');
|
const nodemailer = require('nodemailer');
|
||||||
|
|
||||||
module.exports =
|
class Mailer {
|
||||||
|
sender;
|
||||||
|
started = false;
|
||||||
|
mailer;
|
||||||
|
constructor(host, port, email, password) {
|
||||||
|
this.mailer = nodemailer.createTransport({
|
||||||
|
host: host,
|
||||||
|
port: port,
|
||||||
|
secure: true,
|
||||||
|
auth: {
|
||||||
|
user: email,
|
||||||
|
pass: password
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.sender = email;
|
||||||
|
this.started = true;
|
||||||
|
}
|
||||||
|
async sendMail(recipients, subject, content, contentStripped) {
|
||||||
|
console.log(`sending mail to ${recipients}`);
|
||||||
|
let info = await this.mailer.sendMail({
|
||||||
|
from: `"Todo App" <${this.sender}>`,
|
||||||
|
to: Array.isArray(recipients) ? recipients.join(', ') : recipients,
|
||||||
|
subject: subject,
|
||||||
|
text: contentStripped,
|
||||||
|
html: content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!global.mailer || !global.mailer.started) {
|
||||||
|
if (
|
||||||
|
!Config.config['mail_host'] ||
|
||||||
|
!Config.config['mail_port'] ||
|
||||||
|
!Config.config['mail_username'] ||
|
||||||
|
!Config.config['mail_password']
|
||||||
|
) {
|
||||||
|
console.error(`could not create email account as
|
||||||
|
mail_host, mail_port, mail_username or mail_password is not set.`);
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
global.mailer = new Mailer(
|
||||||
|
Config.config['mail_host'],
|
||||||
|
Config.config['mail_port'],
|
||||||
|
Config.config['mail_username'],
|
||||||
|
Config.config['mail_password']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = global.mailer;
|
||||||
|
|
146
backend/src/todo.js
Normal file
146
backend/src/todo.js
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
const express = require('express');
|
||||||
|
const paginate = require('express-paginate');
|
||||||
|
const Database = require('./db_interface.js');
|
||||||
|
const User = require('./user.js');
|
||||||
|
const { Op } = require('sequelize');
|
||||||
|
|
||||||
|
let router = express.Router();
|
||||||
|
|
||||||
|
router.use(express.json());
|
||||||
|
|
||||||
|
function map_todo(result) {
|
||||||
|
return {
|
||||||
|
id: result.id,
|
||||||
|
content: result.content,
|
||||||
|
tags: result.tags
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_tags(tags) {
|
||||||
|
result = {
|
||||||
|
complete: undefined,
|
||||||
|
required: [],
|
||||||
|
excluded: []
|
||||||
|
};
|
||||||
|
tags.map((tag) => {
|
||||||
|
if (tag === 'complete') {
|
||||||
|
complete = true;
|
||||||
|
} else if (tag === '~complete') {
|
||||||
|
complete = false;
|
||||||
|
} else if (tag.startsWith('~')) {
|
||||||
|
excluded.push(tag);
|
||||||
|
} else {
|
||||||
|
required.push(tag);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const todo_fields = ['currentPage', 'limit'];
|
||||||
|
|
||||||
|
router.use(paginate.middleware(10, 50));
|
||||||
|
router.use('/todos', User.enforce_session_login);
|
||||||
|
router.get('/todos', async (req, res) => {
|
||||||
|
if (!req.query) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: `query must include the fields: ${todo_fields.join(', ')}}`
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let error = [];
|
||||||
|
for (let field of todo_fields) {
|
||||||
|
if (!req.query[field]) {
|
||||||
|
error.push(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (error.length > 0) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: `query must include the fields: ${error.join(', ')}}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let tag_options = {};
|
||||||
|
if (req.query.tags) {
|
||||||
|
let parsed = parse_tags(req.query.tags.split(','));
|
||||||
|
tag_options['tags'] = {
|
||||||
|
[Op.and]: parsed.required,
|
||||||
|
[Op.not]: parsed.excluded
|
||||||
|
};
|
||||||
|
if (parsed.complete !== undefined) {
|
||||||
|
tag_options['complete'] = {
|
||||||
|
[Op.is]: parsed.complete
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(tag_options);
|
||||||
|
let all_todos = await Database.schemas.todo.findAndCountAll({
|
||||||
|
where: {
|
||||||
|
user: req.get('id'),
|
||||||
|
...tag_options
|
||||||
|
},
|
||||||
|
limit: req.query.limit,
|
||||||
|
offset: req.skip
|
||||||
|
});
|
||||||
|
const item_count = all_todos.count;
|
||||||
|
const page_count = Math.ceil(item_count / req.query.limit);
|
||||||
|
res.json({
|
||||||
|
result: all_todos.map(map_todo),
|
||||||
|
currentPage: req.query.currentPage,
|
||||||
|
pageCount: page_count,
|
||||||
|
itemCount: item_count,
|
||||||
|
pages: paginate.getArrayPages(req)(5, page_count, req.query.currentPage)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.use('/todo', User.enforce_session_login);
|
||||||
|
router.get('/todo/:id([a-f0-9-]+)', async (req, res) => {
|
||||||
|
let userid = req.get('id');
|
||||||
|
let id = req.params?.id;
|
||||||
|
|
||||||
|
let match = await Database.schemas.todo.findOne({
|
||||||
|
where: {
|
||||||
|
user: userid,
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!match) {
|
||||||
|
return res.sendStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
result: map_todo(match),
|
||||||
|
tags: get_tags(match.id)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.use('/todo', User.enforce_session_login);
|
||||||
|
router.post('/todo/:id([a-f0-9-]+)', async (req, res) => {
|
||||||
|
let userid = req.get('id');
|
||||||
|
let id = req.params?.id;
|
||||||
|
|
||||||
|
let body = req.body;
|
||||||
|
|
||||||
|
if (!body) {
|
||||||
|
return res.sendStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
let match = await Database.schemas.todo.findOne({
|
||||||
|
where: {
|
||||||
|
user: userid,
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!match) {
|
||||||
|
return res.sendStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
result: map_todo(match),
|
||||||
|
tags: get_tags(match.id)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
router: router
|
||||||
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
const express = require("express");
|
const express = require('express');
|
||||||
const crypto = require("crypto");
|
const crypto = require('crypto');
|
||||||
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');
|
||||||
|
|
||||||
let router = express.Router();
|
let router = express.Router();
|
||||||
|
@ -14,19 +14,19 @@ user_cache = {};
|
||||||
email_cache = {};
|
email_cache = {};
|
||||||
|
|
||||||
async function get_user_details(id) {
|
async function get_user_details(id) {
|
||||||
if (!id || id === "undefined") {
|
if (!id || id === 'undefined') {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
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 } });
|
let user = await Database.schemas.user.findOne({ where: { id: id } });
|
||||||
if (!user) {
|
if (user === null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
user_cache[user.id] = {
|
user_cache[user.id] = {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
password_hash: user.password_hash,
|
password_hash: user.password_hash
|
||||||
};
|
};
|
||||||
email_cache[user.email] = user.id;
|
email_cache[user.email] = user.id;
|
||||||
}
|
}
|
||||||
|
@ -35,19 +35,19 @@ async function get_user_details(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function get_user_details_by_email(email) {
|
async function get_user_details_by_email(email) {
|
||||||
if (!email || email === "undefined") {
|
if (!email || email === 'undefined') {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
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 } });
|
let user = await Database.schemas.user.findOne({ where: { email: email } });
|
||||||
if (!user) {
|
if (user === null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
user_cache[user.id] = {
|
user_cache[user.id] = {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
password_hash: user.password_hash,
|
password_hash: user.password_hash
|
||||||
};
|
};
|
||||||
email_cache[user.email] = user.id;
|
email_cache[user.email] = user.id;
|
||||||
}
|
}
|
||||||
|
@ -55,34 +55,34 @@ async function get_user_details_by_email(email) {
|
||||||
return user_cache[email_cache[email]];
|
return user_cache[email_cache[email]];
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get("/byEmail/:email", async (req, res) => {
|
router.get('/byEmail/:email', async (req, res) => {
|
||||||
if (!req.params?.email) {
|
if (!req.params?.email) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
error: "email is a required parameter",
|
error: 'email is a required parameter'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let user = get_user_details_by_email(req.params.email);
|
let user = get_user_details_by_email(req.params.email);
|
||||||
console.log(user);
|
console.log(user);
|
||||||
if (user != null) {
|
if (user !== undefined && user !== {}) {
|
||||||
res.json({
|
res.json({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function hash(secret, password) {
|
function hash(secret, password, base64 = true) {
|
||||||
let pw_hash = crypto.pbkdf2Sync(
|
let pw_hash = crypto.pbkdf2Sync(
|
||||||
password,
|
password,
|
||||||
secret,
|
secret,
|
||||||
Config.config.key?.iterations || 1000,
|
Config.config.key?.iterations || 1000,
|
||||||
Config.config.key?.length || 64,
|
Config.config.key?.length || 64,
|
||||||
"sha512"
|
'sha512'
|
||||||
);
|
);
|
||||||
|
|
||||||
return pw_hash.toString("base64");
|
return pw_hash.toString(base64 ? 'base64' : 'hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
function verify(secret, password, hash) {
|
function verify(secret, password, hash) {
|
||||||
|
@ -91,10 +91,10 @@ function verify(secret, password, hash) {
|
||||||
secret,
|
secret,
|
||||||
Config.config.key?.iterations || 1000,
|
Config.config.key?.iterations || 1000,
|
||||||
Config.config.key?.length || 64,
|
Config.config.key?.length || 64,
|
||||||
"sha512"
|
'sha512'
|
||||||
);
|
);
|
||||||
|
|
||||||
return hash === pw_hash.toString("base64");
|
return hash === pw_hash.toString('base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
function hash_password(password) {
|
function hash_password(password) {
|
||||||
|
@ -105,9 +105,9 @@ function verify_password(password, hash) {
|
||||||
return verify(Config.config.secret, password, hash);
|
return verify(Config.config.secret, password, hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_session_token(id, password_hash) {
|
function get_session_token(id, password_hash, base64 = true) {
|
||||||
session_entropy[id] = crypto.randomBytes(Config.config.session_entropy || 32);
|
session_entropy[id] = crypto.randomBytes(Config.config.session_entropy || 32);
|
||||||
return hash(session_entropy[id], password_hash);
|
return hash(session_entropy[id], password_hash, base64);
|
||||||
}
|
}
|
||||||
|
|
||||||
function verify_session_token(id, hash, token) {
|
function verify_session_token(id, hash, token) {
|
||||||
|
@ -119,9 +119,9 @@ function verify_session_token(id, hash, token) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function enforce_session_login(req, res, next) {
|
async function enforce_session_login(req, res, next) {
|
||||||
let userid = req.get("id");
|
let userid = req.get('id');
|
||||||
let session_token = req.get("authorization");
|
let session_token = req.get('authorization');
|
||||||
console.log("a", userid, session_token);
|
console.log('a', userid, session_token);
|
||||||
if (!userid || !session_token) {
|
if (!userid || !session_token) {
|
||||||
return res.sendStatus(401);
|
return res.sendStatus(401);
|
||||||
}
|
}
|
||||||
|
@ -129,104 +129,125 @@ async function enforce_session_login(req, res, next) {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.sendStatus(401);
|
return res.sendStatus(401);
|
||||||
}
|
}
|
||||||
let verified_session = verify_session_token(
|
let verified_session = verify_session_token(userid, user.password_hash, session_token);
|
||||||
userid,
|
|
||||||
user.password_hash,
|
|
||||||
session_token
|
|
||||||
);
|
|
||||||
if (!verified_session) {
|
if (!verified_session) {
|
||||||
return res.sendStatus(401);
|
return res.sendStatus(401);
|
||||||
}
|
}
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
router.post("/signup", async (res, req) => {
|
router.post('/signup', 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({
|
||||||
error: "must have email and password fields",
|
error: 'must have email and password fields'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let user = find_user_by_email(req.body?.email);
|
let user = await get_user_details_by_email(req.body?.email);
|
||||||
|
|
||||||
if (user != null) {
|
if (user !== undefined && user !== {}) {
|
||||||
|
console.warn(`user already found: ${JSON.stringify(user)}`);
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
error: `email ${req.body.email} is already in use.`,
|
error: `email ${req.body.email} is already in use.`
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let randomString = "Signup"
|
let match = await Database.schemas.unverifiedUser.findOne({ where: { email: req.body.email } });
|
||||||
for (let i = 0; i < 16, i++) {
|
if (!!match) {
|
||||||
randomString += Math.floor(Math.random() * 10)
|
await Database.schemas.unverifiedUser.destroy({ where: { email: match.email } });
|
||||||
}
|
}
|
||||||
|
let randomString = 'Signup';
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
randomString += Math.floor(Math.random() * 10);
|
||||||
|
}
|
||||||
|
let password_hash = hash_password(req.body.password);
|
||||||
let user = await Database.schemas.unverifiedUser.create({
|
let user = await Database.schemas.unverifiedUser.create({
|
||||||
email: String(req.body.email),
|
email: String(req.body.email),
|
||||||
password_hash: hash_password(req.body.password),
|
password_hash: password_hash,
|
||||||
verificationToken: get_session_token(randomString, )
|
verificationToken: get_session_token(randomString, password_hash, false)
|
||||||
});
|
});
|
||||||
|
const link = `${Config.config.https ? 'https://' : 'http://'}${req.headers.host}/api/user/verify?verification=${
|
||||||
|
user.verificationToken
|
||||||
|
}`;
|
||||||
|
const content = `Click here to verify your sign-up:
|
||||||
|
${link}`;
|
||||||
|
const contentHtml = `<h1>Click here to verify your sign-up:</h1>
|
||||||
|
<p><a href=${link}>${link}</a></p>`;
|
||||||
|
await Mail.sendMail([String(req.body.email)], 'Verify Your Account', contentHtml, content);
|
||||||
return res.sendStatus(204);
|
return res.sendStatus(204);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
router.post("/verify", async (req, res) => {
|
router.get('/verify', async (req, res) => {
|
||||||
if (!req.params?.verification) {
|
if (!req.query?.verification) {
|
||||||
return res.status(400).send(
|
return res.status(400).send(
|
||||||
`<html>
|
`<html>
|
||||||
|
<body>
|
||||||
|
<h1>No Verification Link</h1>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let verification = req.query?.verification;
|
||||||
|
let user = await Database.schemas.unverifiedUser.findOne({ where: { verificationToken: verification } });
|
||||||
|
|
||||||
|
if (user !== undefined && user !== {}) {
|
||||||
|
if (user.verificationToken != verification) {
|
||||||
|
return res.status(404).send(
|
||||||
|
`<html>
|
||||||
<body>
|
<body>
|
||||||
<h1>Unknown Verification Link</h1>
|
<h1>Unknown Verification Link</h1>
|
||||||
</body>
|
</body>
|
||||||
</html>`
|
</html>`
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
let verification =
|
|
||||||
let user = await Database.schemas.unverifiedUser.findOne({ where: { id: id } });
|
|
||||||
|
|
||||||
if (user != null) {
|
|
||||||
let newUser = await Database.schemas.user.create({
|
let newUser = await Database.schemas.user.create({
|
||||||
email: user.email,
|
email: user.email,
|
||||||
password_hash: user.password_hash,
|
password_hash: user.password_hash
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.json({
|
return res.send(`<html>
|
||||||
id: user.id,
|
<body>
|
||||||
email: user.email,
|
<h1>Sign up complete.</h1>
|
||||||
});
|
</body>
|
||||||
|
</html>`);
|
||||||
} else {
|
} else {
|
||||||
return res.status(403).json({
|
return res.status(404).send(`<html>
|
||||||
error: `email ${req.body.email} is already in use.`,
|
<body>
|
||||||
});
|
<h1>Unknown Verification Link</h1>
|
||||||
|
</body>
|
||||||
|
</html>`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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({
|
||||||
error: "must have email and password fields",
|
error: 'must have email and password fields'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let user = await get_user_details_by_email(req.body.email);
|
let user = await get_user_details_by_email(req.body.email);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
error: "incorrect email or password",
|
error: 'incorrect email or password'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let verified = verify_password(req.body.password, user.password_hash);
|
let verified = verify_password(req.body.password, user.password_hash);
|
||||||
|
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
error: "incorrect email or password",
|
error: 'incorrect email or password'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
userid: user.id,
|
userid: user.id,
|
||||||
session_token: get_session_token(user.id, user.password_hash),
|
session_token: get_session_token(user.id, user.password_hash)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/logout", async (req, res) => {
|
router.post('/logout', async (req, res) => {
|
||||||
if (!req.body?.session_token || !req.body?.userid) {
|
if (!req.body?.session_token || !req.body?.userid) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: "must include user id and session token to log out",
|
error: 'must include user id and session token to log out'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,18 +257,14 @@ router.post("/logout", async (req, res) => {
|
||||||
let user = await get_user_details(userid);
|
let user = await get_user_details(userid);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
error: "invalid user data",
|
error: 'invalid user data'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let verified = verify_session_token(
|
let verified = verify_session_token(user.id, user.password_hash, session_token);
|
||||||
user.id,
|
|
||||||
user.password_hash,
|
|
||||||
session_token
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
error: "invalid user data",
|
error: 'invalid user data'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,41 +272,43 @@ router.post("/logout", async (req, res) => {
|
||||||
return res.sendStatus(204);
|
return res.sendStatus(204);
|
||||||
});
|
});
|
||||||
|
|
||||||
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) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: "must have id parameter",
|
error: 'must have id parameter'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let id = req.params?.id;
|
let id = req.params?.id;
|
||||||
console.log(id);
|
console.log(id);
|
||||||
let user = await get_user_details(id);
|
let user = await get_user_details(id);
|
||||||
console.log(user);
|
console.log(user);
|
||||||
if (user != null) {
|
if (user !== undefined && user !== {}) {
|
||||||
return res.json({
|
return res.json({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return res.sendStatus(404);
|
return res.sendStatus(404);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.use("/authorized", enforce_session_login);
|
router.use('/authorized', enforce_session_login);
|
||||||
router.get("/authorized", async (req, res) => {
|
router.get('/authorized', async (req, res) => {
|
||||||
let userid = req.get("id");
|
let userid = req.get('id');
|
||||||
let user = await get_user_details(userid);
|
let user = await get_user_details(userid);
|
||||||
return res.json({
|
return res.json({
|
||||||
authorized: true,
|
authorized: true,
|
||||||
user: {
|
user: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
router: router,
|
router: router,
|
||||||
enforce_session_login: enforce_session_login,
|
enforce_session_login: enforce_session_login,
|
||||||
|
get_user_details: get_user_details,
|
||||||
|
get_user_details_by_email: get_user_details_by_email
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Link from '@material-ui/core/Link';
|
|
||||||
import Grid from '@material-ui/core/Grid';
|
import Grid from '@material-ui/core/Grid';
|
||||||
import GroupIcon from '@material-ui/icons/Group';
|
import GroupIcon from '@material-ui/icons/Group';
|
||||||
|
import SettingsIcon from '@material-ui/icons/Settings';
|
||||||
|
import AssignmentIndIcon from '@material-ui/icons/AssignmentInd';
|
||||||
import Box from '@material-ui/core/Box';
|
import Box from '@material-ui/core/Box';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
|
@ -67,7 +68,7 @@ const LoginLogoutButton = connect(
|
||||||
window.location.pathname = '/account';
|
window.location.pathname = '/account';
|
||||||
}}
|
}}
|
||||||
className={classes.button}>
|
className={classes.button}>
|
||||||
<GroupIcon />
|
<SettingsIcon />
|
||||||
<Typography>Settings</Typography>
|
<Typography>Settings</Typography>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
@ -82,17 +83,28 @@ const LoginLogoutButton = connect(
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<>
|
||||||
variant='contained'
|
<Button
|
||||||
color='primary'
|
variant='contained'
|
||||||
fullWidth
|
color='primary'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.location.pathname = '/login';
|
window.location.pathname = '/login';
|
||||||
}}
|
}}
|
||||||
className={classes.button}>
|
className={classes.button}>
|
||||||
<ExitToAppIcon />
|
<GroupIcon />
|
||||||
<Typography className={classes.typography}>Log In</Typography>
|
<Typography className={classes.typography}>Log In</Typography>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant='contained'
|
||||||
|
color='primary'
|
||||||
|
onClick={() => {
|
||||||
|
window.location.pathname = '/signup';
|
||||||
|
}}
|
||||||
|
className={classes.button}>
|
||||||
|
<AssignmentIndIcon />
|
||||||
|
<Typography className={classes.typography}>Sign Up</Typography>
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,162 @@
|
||||||
|
import { signup } from '../../reducers/login';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
import Grid from '@material-ui/core/Grid';
|
||||||
|
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||||
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
|
import TextField from '@material-ui/core/TextField';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import Visibility from '@material-ui/icons/Visibility';
|
||||||
|
import VisibilityOff from '@material-ui/icons/VisibilityOff';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { withStyles } from '@material-ui/styles';
|
||||||
|
|
||||||
|
const styles = (theme) => {};
|
||||||
|
|
||||||
const SignupPage = (props) => {
|
const SignupPage = (props) => {
|
||||||
return <div></div>;
|
const { classes } = props;
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
const checkSignup = () => {
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
setError(true);
|
||||||
|
} else {
|
||||||
|
setError(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container alignItems='center' justify='center'>
|
||||||
|
<Grid item alignItems='center' justify='center'>
|
||||||
|
<form>
|
||||||
|
<div>
|
||||||
|
<Typography align='center' variant='h6' gutterBottom>
|
||||||
|
Sign Up
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
autoComplete='new-email'
|
||||||
|
label='Email'
|
||||||
|
name='email'
|
||||||
|
type='email'
|
||||||
|
value={email}
|
||||||
|
variant='outlined'
|
||||||
|
fullWidth
|
||||||
|
onChange={(event) => {
|
||||||
|
return setEmail(event.target.value ?? '');
|
||||||
|
}}
|
||||||
|
onKeyPress={(event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
checkSignup();
|
||||||
|
if (!error) {
|
||||||
|
props.signup(email, password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
autoComplete='new-password'
|
||||||
|
label='Password'
|
||||||
|
name='password'
|
||||||
|
type={visible ? 'text' : 'password'}
|
||||||
|
value={password}
|
||||||
|
variant='outlined'
|
||||||
|
fullWidth
|
||||||
|
onChange={(event) => {
|
||||||
|
setPassword(event.target.value ?? '');
|
||||||
|
}}
|
||||||
|
onKeyPress={(event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
checkSignup();
|
||||||
|
if (!error) {
|
||||||
|
props.signup(email, password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position='end'>
|
||||||
|
<IconButton
|
||||||
|
aria-label='toggle password visibility'
|
||||||
|
onClick={() => {
|
||||||
|
return setVisible(!visible);
|
||||||
|
}}
|
||||||
|
edge='end'>
|
||||||
|
{visible ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
autoComplete='confirm-password'
|
||||||
|
label='Confirm Password'
|
||||||
|
name='confirm-password'
|
||||||
|
type={visible ? 'text' : 'password'}
|
||||||
|
error={error}
|
||||||
|
value={confirmPassword}
|
||||||
|
variant='outlined'
|
||||||
|
fullWidth
|
||||||
|
onChange={(event) => {
|
||||||
|
setConfirmPassword(event.target.value ?? '');
|
||||||
|
}}
|
||||||
|
onKeyPress={(event) => {
|
||||||
|
checkSignup();
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
if (!error) {
|
||||||
|
props.signup(email, password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position='end'>
|
||||||
|
<IconButton
|
||||||
|
aria-label='toggle confirm password visibility'
|
||||||
|
onClick={() => {
|
||||||
|
return setVisible(!visible);
|
||||||
|
}}
|
||||||
|
edge='end'>
|
||||||
|
{visible ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
id='login-button'
|
||||||
|
variant='contained'
|
||||||
|
color='primary'
|
||||||
|
onClick={() => {
|
||||||
|
checkSignup();
|
||||||
|
if (!error) {
|
||||||
|
props.signup(email, password);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
Sign Up
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SignupPage;
|
export default connect(
|
||||||
|
(state) => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
(dispatch, props) => {
|
||||||
|
return {
|
||||||
|
signup: (email, password) => {
|
||||||
|
dispatch(signup(email, password));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)(withStyles(styles)(SignupPage));
|
||||||
|
|
|
@ -54,6 +54,23 @@ export const login = createAsyncAction((dispatch, getState, config, email, passw
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const signup = createAsyncAction((dispatch, getState, config, email, password) => {
|
||||||
|
axios
|
||||||
|
.post(`${config.apiUrl}/user/signup`, {
|
||||||
|
email: email,
|
||||||
|
password: password
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
(success) => {
|
||||||
|
console.error('success', success);
|
||||||
|
window.location.pathname = '/login';
|
||||||
|
},
|
||||||
|
(reject) => {
|
||||||
|
console.error(reject);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export const forgotPassword = createAsyncAction((dispatch, getState, config, email) => {});
|
export const forgotPassword = createAsyncAction((dispatch, getState, config, email) => {});
|
||||||
|
|
||||||
export const logout = createAsyncAction((dispatch, getState, config) => {
|
export const logout = createAsyncAction((dispatch, getState, config) => {
|
||||||
|
|
Loading…
Reference in a new issue