Added basic user registration endpoint and password-protected a single endpoint. No sessions yet.

pull/31/head
Sander Vocke 5 years ago
parent 87af6e18a4
commit c319d0bce1
  1. 38
      client/src/api.ts
  2. 119
      server/app.ts
  3. 0
      server/endpoints/AlbumDetails.ts
  4. 0
      server/endpoints/ArtistDetails.ts
  5. 0
      server/endpoints/CreateAlbum.ts
  6. 0
      server/endpoints/CreateArtist.ts
  7. 0
      server/endpoints/CreateSong.ts
  8. 0
      server/endpoints/CreateTag.ts
  9. 0
      server/endpoints/DeleteTag.ts
  10. 0
      server/endpoints/MergeTag.ts
  11. 0
      server/endpoints/ModifyAlbum.ts
  12. 0
      server/endpoints/ModifyArtist.ts
  13. 0
      server/endpoints/ModifySong.ts
  14. 0
      server/endpoints/ModifyTag.ts
  15. 0
      server/endpoints/Query.ts
  16. 41
      server/endpoints/RegisterUser.ts
  17. 0
      server/endpoints/SongDetails.ts
  18. 0
      server/endpoints/TagDetails.ts
  19. 20
      server/migrations/20201110170100_add_users.ts
  20. 32
      server/package-lock.json
  21. 3
      server/package.json

@ -318,4 +318,40 @@ export interface MergeTagRequest { }
export interface MergeTagResponse { }
export function checkMergeTagRequest(req: any): boolean {
return true;
}
}
// Register a user (POST).
// TODO: add e-mail verification.
export const RegisterUserEndpoint = '/register';
export interface RegisterUserRequest {
email: string,
password: string,
}
export interface RegisterUserResponse { }
export function checkPassword(password: string): boolean {
const result = (password.length < 32) &&
(password.length >= 8) &&
/^[\x00-\x7F]*$/.test(password) && // is ASCII
(/[a-z]/g.test(password)) && // has lowercase
(/[A-Z]/g.test(password)) && // has uppercase
(/[0-9]/g.test(password)) && // has number
(/[!@#\$%\^&\*\(\)_\+/]/g.test(password)) // has special character;
console.log("Password check for ", password, ": ", result);
return result;
}
export function checkEmail(email: string): boolean {
const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const result = re.test(String(email).toLowerCase());
console.log("Email check for ", email, ": ", result);
return result;
}
export function checkRegisterUserRequest(req: any): boolean {
return "body" in req &&
"email" in req.body &&
"password" in req.body &&
checkEmail(req.body.email) &&
checkPassword(req.body.password);
}
// Note: Login is handled by Passport.js, so it is not explicitly written here.

@ -2,33 +2,39 @@ const bodyParser = require('body-parser');
import * as api from '../client/src/api';
import Knex from 'knex';
import { CreateSongEndpointHandler } from './endpoints/CreateSongEndpointHandler';
import { CreateArtistEndpointHandler } from './endpoints/CreateArtistEndpointHandler';
import { QueryEndpointHandler } from './endpoints/QueryEndpointHandler';
import { ArtistDetailsEndpointHandler } from './endpoints/ArtistDetailsEndpointHandler'
import { SongDetailsEndpointHandler } from './endpoints/SongDetailsEndpointHandler';
import { ModifyArtistEndpointHandler } from './endpoints/ModifyArtistEndpointHandler';
import { ModifySongEndpointHandler } from './endpoints/ModifySongEndpointHandler';
import { CreateTagEndpointHandler } from './endpoints/CreateTagEndpointHandler';
import { ModifyTagEndpointHandler } from './endpoints/ModifyTagEndpointHandler';
import { TagDetailsEndpointHandler } from './endpoints/TagDetailsEndpointHandler';
import { CreateAlbumEndpointHandler } from './endpoints/CreateAlbumEndpointHandler';
import { ModifyAlbumEndpointHandler } from './endpoints/ModifyAlbumEndpointHandler';
import { AlbumDetailsEndpointHandler } from './endpoints/AlbumDetailsEndpointHandler';
import { DeleteTagEndpointHandler } from './endpoints/DeleteTagEndpointHandler';
import { MergeTagEndpointHandler } from './endpoints/MergeTagEndpointHandler';
import { CreateSongEndpointHandler } from './endpoints/CreateSong';
import { CreateArtistEndpointHandler } from './endpoints/CreateArtist';
import { QueryEndpointHandler } from './endpoints/Query';
import { ArtistDetailsEndpointHandler } from './endpoints/ArtistDetails'
import { SongDetailsEndpointHandler } from './endpoints/SongDetails';
import { ModifyArtistEndpointHandler } from './endpoints/ModifyArtist';
import { ModifySongEndpointHandler } from './endpoints/ModifySong';
import { CreateTagEndpointHandler } from './endpoints/CreateTag';
import { ModifyTagEndpointHandler } from './endpoints/ModifyTag';
import { TagDetailsEndpointHandler } from './endpoints/TagDetails';
import { CreateAlbumEndpointHandler } from './endpoints/CreateAlbum';
import { ModifyAlbumEndpointHandler } from './endpoints/ModifyAlbum';
import { AlbumDetailsEndpointHandler } from './endpoints/AlbumDetails';
import { DeleteTagEndpointHandler } from './endpoints/DeleteTag';
import { MergeTagEndpointHandler } from './endpoints/MergeTag';
import { RegisterUserEndpointHandler } from './endpoints/RegisterUser';
import * as endpointTypes from './endpoints/types';
import { sha512 } from 'js-sha512';
const invokeHandler = (handler:endpointTypes.EndpointHandler, knex: Knex) => {
// For authentication
var passport = require('passport');
var Strategy = require('passport-local').Strategy;
const invokeHandler = (handler: endpointTypes.EndpointHandler, knex: Knex) => {
return async (req: any, res: any) => {
console.log("Incoming", req.method, " @ ", req.url);
await handler(req, res, knex)
.catch(endpointTypes.catchUnhandledErrors)
.catch((_e:endpointTypes.EndpointError) => {
let e:endpointTypes.EndpointError = _e;
console.log("Error handling request: ", e.internalMessage);
res.sendStatus(e.httpStatus);
})
.catch(endpointTypes.catchUnhandledErrors)
.catch((_e: endpointTypes.EndpointError) => {
let e: endpointTypes.EndpointError = _e;
console.log("Error handling request: ", e.internalMessage);
res.sendStatus(e.httpStatus);
})
console.log("Finished handling", req.method, "@", req.url);
};
}
@ -37,26 +43,63 @@ const SetupApp = (app: any, knex: Knex, apiBaseUrl: string) => {
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const invokeWithKnex = (handler: endpointTypes.EndpointHandler) => {
// Set up auth. See: https://github.com/passport/express-4.x-local-example.git
passport.use(new Strategy(
function (email: string, password: string, cb: any) {
(async () => {
try {
const user = await knex.select(['email', 'passwordHash', 'id'])
.from('users')
.where({ 'email': email })
.then((users: any) => users[0]);
if (!user) { cb(null, false); }
if (sha512(password) != user.passwordHash) {
return cb(null, false);
}
return cb(null, user);
} catch (error) { cb(error); }
})();
}));
// passport.serializeUser(function (user: any, cb: any) {
// cb(null, user.id);
// });
// passport.deserializeUser(function (id: number, cb: any) {
// (async () => {
// try {
// const user = await knex.select(['email', 'passwordHash', 'id'])
// .from('users')
// .where({ 'id': id })
// .then((users: any) => users[0]);
// if (!user) { cb(null, false); }
// return cb(null, user);
// } catch (error) { cb(error); }
// })();
// });
app.use(passport.initialize());
//app.use(passport.session());
const _invoke = (handler: endpointTypes.EndpointHandler) => {
return invokeHandler(handler, knex);
}
// Set up REST API endpoints
app.post(apiBaseUrl + api.CreateSongEndpoint, invokeWithKnex(CreateSongEndpointHandler));
app.post(apiBaseUrl + api.QueryEndpoint, invokeWithKnex(QueryEndpointHandler));
app.post(apiBaseUrl + api.CreateArtistEndpoint, invokeWithKnex(CreateArtistEndpointHandler));
app.put(apiBaseUrl + api.ModifyArtistEndpoint, invokeWithKnex(ModifyArtistEndpointHandler));
app.put(apiBaseUrl + api.ModifySongEndpoint, invokeWithKnex(ModifySongEndpointHandler));
app.get(apiBaseUrl + api.SongDetailsEndpoint, invokeWithKnex(SongDetailsEndpointHandler));
app.get(apiBaseUrl + api.ArtistDetailsEndpoint, invokeWithKnex(ArtistDetailsEndpointHandler));
app.post(apiBaseUrl + api.CreateTagEndpoint, invokeWithKnex(CreateTagEndpointHandler));
app.put(apiBaseUrl + api.ModifyTagEndpoint, invokeWithKnex(ModifyTagEndpointHandler));
app.get(apiBaseUrl + api.TagDetailsEndpoint, invokeWithKnex(TagDetailsEndpointHandler));
app.post(apiBaseUrl + api.CreateAlbumEndpoint, invokeWithKnex(CreateAlbumEndpointHandler));
app.put(apiBaseUrl + api.ModifyAlbumEndpoint, invokeWithKnex(ModifyAlbumEndpointHandler));
app.get(apiBaseUrl + api.AlbumDetailsEndpoint, invokeWithKnex(AlbumDetailsEndpointHandler));
app.delete(apiBaseUrl + api.DeleteTagEndpoint, invokeWithKnex(DeleteTagEndpointHandler));
app.post(apiBaseUrl + api.MergeTagEndpoint, invokeWithKnex(MergeTagEndpointHandler));
app.post(apiBaseUrl + api.CreateSongEndpoint, _invoke(CreateSongEndpointHandler));
app.post(apiBaseUrl + api.QueryEndpoint, _invoke(QueryEndpointHandler));
app.post(apiBaseUrl + api.CreateArtistEndpoint, _invoke(CreateArtistEndpointHandler));
app.put(apiBaseUrl + api.ModifyArtistEndpoint, _invoke(ModifyArtistEndpointHandler));
app.put(apiBaseUrl + api.ModifySongEndpoint, _invoke(ModifySongEndpointHandler));
app.get(apiBaseUrl + api.SongDetailsEndpoint, passport.authenticate('local', { session: false }), _invoke(SongDetailsEndpointHandler));
app.get(apiBaseUrl + api.ArtistDetailsEndpoint, _invoke(ArtistDetailsEndpointHandler));
app.post(apiBaseUrl + api.CreateTagEndpoint, _invoke(CreateTagEndpointHandler));
app.put(apiBaseUrl + api.ModifyTagEndpoint, _invoke(ModifyTagEndpointHandler));
app.get(apiBaseUrl + api.TagDetailsEndpoint, _invoke(TagDetailsEndpointHandler));
app.post(apiBaseUrl + api.CreateAlbumEndpoint, _invoke(CreateAlbumEndpointHandler));
app.put(apiBaseUrl + api.ModifyAlbumEndpoint, _invoke(ModifyAlbumEndpointHandler));
app.get(apiBaseUrl + api.AlbumDetailsEndpoint, _invoke(AlbumDetailsEndpointHandler));
app.delete(apiBaseUrl + api.DeleteTagEndpoint, _invoke(DeleteTagEndpointHandler));
app.post(apiBaseUrl + api.MergeTagEndpoint, _invoke(MergeTagEndpointHandler));
app.post(apiBaseUrl + api.RegisterUserEndpoint, _invoke(RegisterUserEndpointHandler));
}
export { SetupApp }

@ -0,0 +1,41 @@
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
import Knex from 'knex';
import { sha512 } from 'js-sha512';
export const RegisterUserEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkRegisterUserRequest(req)) {
const e: EndpointError = {
internalMessage: 'Invalid RegisterUser request: ' + JSON.stringify(req.body),
httpStatus: 400
};
throw e;
}
const reqObject: api.RegisterUserRequest = req.body;
console.log("Register User: ", reqObject);
await knex.transaction(async (trx) => {
try {
// FIXME check if the user already exists
// Create the new user.
const passwordHash = sha512(reqObject.password);
const userId = (await trx('users')
.insert({
email: reqObject.email,
passwordHash: passwordHash,
})
.returning('id') // Needed for Postgres
)[0];
// Respond to the request.
res.status(200).send();
} catch (e) {
catchUnhandledErrors(e);
trx.rollback();
}
})
}

@ -0,0 +1,20 @@
import * as Knex from "knex";
export async function up(knex: Knex): Promise<void> {
// Users table.
await knex.schema.createTable(
'users',
(table: any) => {
table.increments('id');
table.string('email');
table.string('passwordHash')
}
)
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTable('users');
}

@ -1936,6 +1936,11 @@
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz",
"integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA=="
},
"js-sha512": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz",
"integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ=="
},
"jsbi": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.3.tgz",
@ -2756,6 +2761,28 @@
"resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
},
"passport": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz",
"integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==",
"requires": {
"passport-strategy": "1.x.x",
"pause": "0.0.1"
}
},
"passport-local": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
"integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=",
"requires": {
"passport-strategy": "1.x.x"
}
},
"passport-strategy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
"integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ="
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@ -2789,6 +2816,11 @@
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
"integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA="
},
"pause": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",

@ -13,12 +13,15 @@
"chai-http": "^4.3.0",
"express": "^4.16.4",
"jasmine": "^3.5.0",
"js-sha512": "^0.8.0",
"knex": "^0.21.5",
"mssql": "^6.2.1",
"mysql": "^2.18.1",
"mysql2": "^2.1.0",
"nodemon": "^2.0.4",
"oracledb": "^5.0.0",
"passport": "^0.4.1",
"passport-local": "^1.0.0",
"pg": "^8.3.3",
"sqlite3": "^5.0.0",
"ts-node": "^8.10.2",

Loading…
Cancel
Save