typescript(lunargame/api): added eslint
Signed-off-by: prescientmoon <git@moonythm.dev>
This commit is contained in:
parent
013b17dc22
commit
02bf17d559
25
typescript/lunargame/api/.eslintrc.json
Normal file
25
typescript/lunargame/api/.eslintrc.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"env": {
|
||||||
|
"es6": true,
|
||||||
|
"node": true,
|
||||||
|
"jest": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"prettier/@typescript-eslint",
|
||||||
|
"plugin:prettier/recommended"
|
||||||
|
],
|
||||||
|
"globals": {
|
||||||
|
"Atomics": "readonly",
|
||||||
|
"SharedArrayBuffer": "readonly"
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2018,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/explicit-function-return-type": 0,
|
||||||
|
"@typescript-eslint/no-object-literal-type-assertion": 0
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"printWidth": 80,
|
"printWidth": 100,
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"semi": false
|
"semi": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,5 +2,7 @@
|
||||||
"eslint.enable": true,
|
"eslint.enable": true,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"prettier.eslintIntegration": true,
|
"prettier.eslintIntegration": true,
|
||||||
"explorer.autoReveal": false
|
"explorer.autoReveal": false,
|
||||||
|
"eslint.autoFixOnSave": true,
|
||||||
|
"eslint.validate": ["javascript", { "language": "typescript", "autoFix": true }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ exports.up = knex => {
|
||||||
// the password of the user
|
// the password of the user
|
||||||
table.text('password').notNullable()
|
table.text('password').notNullable()
|
||||||
|
|
||||||
// the password encription
|
// the password encryption
|
||||||
table.text('password_encription').notNullable()
|
table.text('passwordEncryption').notNullable()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
821
typescript/lunargame/api/package-lock.json
generated
821
typescript/lunargame/api/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -12,6 +12,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcryptjs": "^2.4.2",
|
"@types/bcryptjs": "^2.4.2",
|
||||||
"@types/dotenv": "^6.1.1",
|
"@types/dotenv": "^6.1.1",
|
||||||
|
"@types/faker": "^4.1.5",
|
||||||
"@types/jest": "^24.0.17",
|
"@types/jest": "^24.0.17",
|
||||||
"@types/joi": "^14.3.3",
|
"@types/joi": "^14.3.3",
|
||||||
"@types/koa": "^2.0.49",
|
"@types/koa": "^2.0.49",
|
||||||
|
@ -20,12 +21,18 @@
|
||||||
"@types/koa-session": "^5.10.1",
|
"@types/koa-session": "^5.10.1",
|
||||||
"@types/koa__cors": "^2.2.3",
|
"@types/koa__cors": "^2.2.3",
|
||||||
"@types/node": "^12.0.10",
|
"@types/node": "^12.0.10",
|
||||||
"@types/nodemailer": "^6.2.0",
|
|
||||||
"@types/supertest": "^2.0.8",
|
"@types/supertest": "^2.0.8",
|
||||||
"@types/uuid": "^3.4.5",
|
"@types/uuid": "^3.4.5",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^1.13.0",
|
||||||
|
"@typescript-eslint/parser": "^1.13.0",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
|
"eslint": "^6.1.0",
|
||||||
|
"eslint-config-prettier": "^6.0.0",
|
||||||
|
"eslint-plugin-prettier": "^3.1.0",
|
||||||
|
"faker": "^4.1.0",
|
||||||
"jest": "^24.8.0",
|
"jest": "^24.8.0",
|
||||||
"nodemon": "^1.19.1",
|
"nodemon": "^1.19.1",
|
||||||
|
"prettier": "^1.18.2",
|
||||||
"sqlite3": "^4.0.9",
|
"sqlite3": "^4.0.9",
|
||||||
"ts-jest": "^24.0.2",
|
"ts-jest": "^24.0.2",
|
||||||
"ts-node": "^8.3.0",
|
"ts-node": "^8.3.0",
|
||||||
|
@ -37,13 +44,13 @@
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"dotenv": "^8.0.0",
|
"dotenv": "^8.0.0",
|
||||||
"joi": "^14.3.1",
|
"joi": "^14.3.1",
|
||||||
|
"joi-extract-type": "^15.0.0",
|
||||||
"knex": "^0.18.1",
|
"knex": "^0.18.1",
|
||||||
"koa": "^2.7.0",
|
"koa": "^2.7.0",
|
||||||
"koa-bodyparser": "^4.2.1",
|
"koa-bodyparser": "^4.2.1",
|
||||||
"koa-router": "^7.4.0",
|
"koa-router": "^7.4.0",
|
||||||
"koa-session": "^5.12.0",
|
"koa-session": "^5.12.0",
|
||||||
"koa-session-knex-store": "^1.1.2",
|
"koa-session-knex-store": "^1.1.2",
|
||||||
"nodemailer": "^6.2.1",
|
|
||||||
"pg": "^7.11.0",
|
"pg": "^7.11.0",
|
||||||
"supertest": "^4.0.2",
|
"supertest": "^4.0.2",
|
||||||
"uuid": "^3.3.2"
|
"uuid": "^3.3.2"
|
||||||
|
|
|
@ -8,15 +8,8 @@ describe('The randomElement function', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should throw an error when passing an empty array', () => {
|
test('should throw an error when passing an empty array', () => {
|
||||||
let error: Error | undefined
|
expect(() => {
|
||||||
|
|
||||||
try {
|
|
||||||
randomElement([])
|
randomElement([])
|
||||||
} catch (catchedError) {
|
}).toThrow()
|
||||||
//
|
|
||||||
error = catchedError
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error).toBeTruthy()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
4
typescript/lunargame/api/src/modules/auth/constants.ts
Normal file
4
typescript/lunargame/api/src/modules/auth/constants.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { passwordEncryption } from './types/passwordEncryption'
|
||||||
|
|
||||||
|
// i made a separate constant to prevent duplication
|
||||||
|
export const defaultEncryptionMethod: passwordEncryption = 'bcrypt'
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { checkPassword } from './checkPassword'
|
||||||
|
import { passwordEncryption } from '../types/passwordEncryption'
|
||||||
|
|
||||||
|
describe('The checkPassword helper', () => {
|
||||||
|
const pass = 'this is a test password'
|
||||||
|
|
||||||
|
test("should throw an error if the encryption method doesn't exist", () => {
|
||||||
|
expect(() => {
|
||||||
|
checkPassword(pass, pass, '12212' as passwordEncryption)
|
||||||
|
}).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should return true if the password matches the hash and the encryption = plain', () => {
|
||||||
|
expect(checkPassword(pass, pass, 'plain')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('shoud return false if the password is wrong and the encryption = plain', () => {
|
||||||
|
expect(checkPassword(pass, pass + 'something', 'plain')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { passwordEncryption } from '../types/passwordEncryption'
|
||||||
|
import { HttpError } from '../../network/classes/HttpError'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparesa apssword with it's hash
|
||||||
|
*
|
||||||
|
* @param hash The hash of the password
|
||||||
|
* @param password The actual password
|
||||||
|
* @param encryption The encription of the password
|
||||||
|
*/
|
||||||
|
export const checkPassword = (
|
||||||
|
hash: string,
|
||||||
|
password: string,
|
||||||
|
encryption: passwordEncryption = 'plain'
|
||||||
|
) => {
|
||||||
|
if (encryption === 'plain') {
|
||||||
|
return hash === password
|
||||||
|
} else {
|
||||||
|
throw new HttpError(400, `Encription ${encryption} doesn't exist`)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { internet } from 'faker'
|
||||||
|
import { encryptPassword } from './encryptPassword'
|
||||||
|
import { compare } from 'bcryptjs'
|
||||||
|
|
||||||
|
describe('The encryptPassword helper', () => {
|
||||||
|
test("should return the same password if the method is 'plain'", async () => {
|
||||||
|
const password = internet.password()
|
||||||
|
const hash = await encryptPassword(password, 'plain')
|
||||||
|
|
||||||
|
expect(hash).toBe(password)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should return a mactching hash if the method is 'bcrypt'", async () => {
|
||||||
|
const password = internet.password()
|
||||||
|
|
||||||
|
// the amount of rounds is small because this is just a test
|
||||||
|
const hash = await encryptPassword(password, 'bcrypt', 3)
|
||||||
|
const match = await compare(password, hash)
|
||||||
|
|
||||||
|
expect(match).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { passwordEncryption } from '../types/passwordEncryption'
|
||||||
|
import { genSalt, hash } from 'bcryptjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encypts a string
|
||||||
|
*
|
||||||
|
* @param password The password to encrypt
|
||||||
|
* @param method The method to encrypt the password with
|
||||||
|
* @param rounds The salting rounds (for bcrypt only)
|
||||||
|
*/
|
||||||
|
export const encryptPassword = async (
|
||||||
|
password: string,
|
||||||
|
method: passwordEncryption,
|
||||||
|
rounds = 10
|
||||||
|
) => {
|
||||||
|
if (method === 'bcrypt') {
|
||||||
|
const salt = await genSalt(rounds)
|
||||||
|
const result = await hash(password, salt)
|
||||||
|
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
return password
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Middleware } from 'koa'
|
||||||
|
import { HttpError } from '../../network/classes/HttpError'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middlware wich throws an error if the user isn't logged in
|
||||||
|
*/
|
||||||
|
export const isAuthorized = (): Middleware => (context, next) => {
|
||||||
|
if (context.session.uid !== undefined) {
|
||||||
|
return next()
|
||||||
|
} else {
|
||||||
|
throw new HttpError(401)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Context } from 'koa'
|
||||||
|
import { isUnauthorized } from './isUnauthorized'
|
||||||
|
|
||||||
|
describe('The isUnauthorized middleware', () => {
|
||||||
|
const fakeNext = () => async () => {}
|
||||||
|
|
||||||
|
test('should throw an error if the user is logged in', () => {
|
||||||
|
const fakeContext = ({
|
||||||
|
session: {
|
||||||
|
uid: 7
|
||||||
|
}
|
||||||
|
} as unknown) as Context
|
||||||
|
|
||||||
|
expect(() => isUnauthorized()(fakeContext, fakeNext())).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should call next if the user isn't logged in", () => {
|
||||||
|
const fakeContext = {
|
||||||
|
session: {}
|
||||||
|
} as Context
|
||||||
|
|
||||||
|
const next = jest.fn(fakeNext())
|
||||||
|
|
||||||
|
isUnauthorized()(fakeContext, next)
|
||||||
|
|
||||||
|
expect(next).toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Middleware } from 'koa'
|
||||||
|
import { HttpError } from '../../network/classes/HttpError'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware wich throws an error if the user is logged in
|
||||||
|
*/
|
||||||
|
export const isUnauthorized = (): Middleware => (context, next) => {
|
||||||
|
if (context.session.uid === undefined) {
|
||||||
|
return next()
|
||||||
|
} else {
|
||||||
|
throw new HttpError(401)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { isAuthorized } from './isAuthorized'
|
||||||
|
import { Context } from 'koa'
|
||||||
|
|
||||||
|
describe('The isAuthorized middleware', () => {
|
||||||
|
const fakeNext = () => async () => {}
|
||||||
|
|
||||||
|
test("should throw an error if the user isn't logged in", () => {
|
||||||
|
const fakeContext = {
|
||||||
|
session: {}
|
||||||
|
} as Context
|
||||||
|
|
||||||
|
expect(() => isAuthorized()(fakeContext, fakeNext())).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should call next if the user is logged in', () => {
|
||||||
|
const fakeContext = ({
|
||||||
|
session: {
|
||||||
|
uid: Math.random()
|
||||||
|
}
|
||||||
|
} as unknown) as Context
|
||||||
|
|
||||||
|
const next = jest.fn(fakeNext())
|
||||||
|
|
||||||
|
isAuthorized()(fakeContext, next)
|
||||||
|
|
||||||
|
expect(next).toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,32 @@
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
|
import { name, random, internet } from 'faker'
|
||||||
|
import { createAccount } from './createAccount'
|
||||||
|
import { connection } from '../../db/connection'
|
||||||
|
import { SignupBody } from '../schemas/SignupBody'
|
||||||
|
|
||||||
|
describe('The createAccount query', () => {
|
||||||
|
test('should return the id of the account and add it to the db', async () => {
|
||||||
|
const email = internet.email()
|
||||||
|
const username = name.firstName()
|
||||||
|
const password = random.alphaNumeric(10)
|
||||||
|
|
||||||
|
const result = await createAccount({
|
||||||
|
email,
|
||||||
|
name: username,
|
||||||
|
password,
|
||||||
|
passwordEncryption: 'plain'
|
||||||
|
})
|
||||||
|
|
||||||
|
const account = await connection
|
||||||
|
.from('account')
|
||||||
|
.select<Required<SignupBody>>(['email', 'name', 'password'])
|
||||||
|
.where({
|
||||||
|
id: result
|
||||||
|
})
|
||||||
|
.first()
|
||||||
|
|
||||||
|
expect(account.name).toBe(username)
|
||||||
|
expect(account.email).toBe(email)
|
||||||
|
expect(account.password).toBe(password)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { connection } from '../../db/connection'
|
||||||
|
import { DbAccount } from '../types/Account'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a new user into the db
|
||||||
|
*
|
||||||
|
* @param user The user object to insert
|
||||||
|
*/
|
||||||
|
export const createAccount = async (user: DbAccount): Promise<number> => {
|
||||||
|
const result = await connection.from('account').insert({
|
||||||
|
...user
|
||||||
|
})
|
||||||
|
|
||||||
|
return result[0]
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { getPasswordByEmail } from './getPasswordByEmail'
|
||||||
|
import { mockAccounts } from '../../../../test/seeds/01_create-account'
|
||||||
|
import { connection } from '../../db/connection'
|
||||||
|
|
||||||
|
describe('The getPasswordByName query', () => {
|
||||||
|
test('should return the correct password & encryption for a mock account', async () => {
|
||||||
|
await connection.seed.run()
|
||||||
|
|
||||||
|
for (const account of mockAccounts) {
|
||||||
|
const result = await getPasswordByEmail(account.email)
|
||||||
|
|
||||||
|
expect(result.password).toBe(account.password)
|
||||||
|
expect(result.passwordEncryption).toBe(account.passwordEncryption)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { connection } from '../../db/connection'
|
||||||
|
import { passwordEncryption } from '../types/passwordEncryption'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of the getPasswordByName query
|
||||||
|
*/
|
||||||
|
export interface PasswordByEmailResult {
|
||||||
|
password: string
|
||||||
|
passwordEncryption: passwordEncryption
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the password, passwordEncryption and id of an account from it's email
|
||||||
|
*
|
||||||
|
* @param email The email of the account
|
||||||
|
*/
|
||||||
|
export const getPasswordByEmail = (email: string): Promise<PasswordByEmailResult> => {
|
||||||
|
return connection
|
||||||
|
.from('account')
|
||||||
|
.select('password', 'passwordEncryption', 'id')
|
||||||
|
.where({
|
||||||
|
email
|
||||||
|
})
|
||||||
|
.first()
|
||||||
|
}
|
|
@ -1,26 +1,95 @@
|
||||||
import supertest from 'supertest'
|
import supertest from 'supertest'
|
||||||
import { app } from '../../../server'
|
import { app } from '../../../server'
|
||||||
import { loggedInAgent } from '../../../../test/utils/loggedInAgent'
|
import { loggedInAgent } from '../../../../test/utils/loggedInAgent'
|
||||||
|
import { mockAccounts } from '../../../../test/seeds/01_create-account'
|
||||||
|
import { random, internet } from 'faker'
|
||||||
|
import { LoginReponseBody } from '../types/LoginReponseBody'
|
||||||
|
import { defaultEncryptionMethod } from '../constants'
|
||||||
|
|
||||||
describe('The /auth route', () => {
|
describe('The /auth route', () => {
|
||||||
|
// used to make requests
|
||||||
let request = supertest(app.callback())
|
let request = supertest(app.callback())
|
||||||
|
|
||||||
test('should return undefined if the user was not logged in', async () => {
|
describe(`The POST method on the /login subroute`, () => {
|
||||||
const res = await request.get('/auth')
|
test('should throw an error if the password field is empty', async () => {
|
||||||
|
const response = await request.post('/auth/login').send({
|
||||||
|
name: mockAccounts[0].name
|
||||||
|
})
|
||||||
|
|
||||||
expect(res.body.uid).toBe(undefined)
|
expect(response.status).not.toBe(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should throw an error if the name field is empty', async () => {
|
||||||
|
const response = await request.post('/auth/login').send({
|
||||||
|
password: mockAccounts[0].password
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).not.toBe(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should throw an error if the password is wrong', async () => {
|
||||||
|
const response = await request.post('/auth/login').send({
|
||||||
|
name: mockAccounts[0].name,
|
||||||
|
password: mockAccounts[0].password + 'something'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).not.toBe(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should work just fine when the password is correct', async () => {
|
||||||
|
for (const account of mockAccounts) {
|
||||||
|
const response = await request.post('/auth/login').send({
|
||||||
|
email: account.email,
|
||||||
|
password: account.password
|
||||||
|
})
|
||||||
|
|
||||||
|
// i'm making a separate constant for vsc to help me
|
||||||
|
const body: LoginReponseBody = response.body
|
||||||
|
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(body.uid).not.toBe(undefined)
|
||||||
|
expect(body.encryption).toBe(account.passwordEncryption)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.only('should return the uid form the session while logged in', async () => {
|
describe(`The GET method on the / subroute`, () => {
|
||||||
const uid = 7
|
test('should return undefined if the user was not logged in', async () => {
|
||||||
|
const res = await request.get('/auth')
|
||||||
|
|
||||||
const [agent, cookie] = await loggedInAgent(
|
expect(res.body.uid).toBe(undefined)
|
||||||
supertest.agent(app.callback()),
|
})
|
||||||
uid
|
|
||||||
)
|
|
||||||
|
|
||||||
const res = await agent.get('/auth').set('cookie', cookie)
|
test('should return the uid form the session while logged in', async () => {
|
||||||
|
const [agent, cookie] = await loggedInAgent(supertest.agent(app.callback()), {
|
||||||
|
email: mockAccounts[0].email,
|
||||||
|
password: mockAccounts[0].password
|
||||||
|
})
|
||||||
|
|
||||||
expect(res.body.uid).toBe(uid)
|
const response = await agent.get('/auth').set('cookie', cookie)
|
||||||
|
|
||||||
|
expect(response.body.uid).not.toBe(undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('The POST method on the /signup subroute', () => {
|
||||||
|
test('should return the email name and the encrytion', async () => {
|
||||||
|
const username = random.alphaNumeric(7)
|
||||||
|
const password = random.alphaNumeric(5)
|
||||||
|
const email = internet.email()
|
||||||
|
|
||||||
|
const response = await request.post('/auth/signup').send({
|
||||||
|
name: username,
|
||||||
|
email,
|
||||||
|
password
|
||||||
|
})
|
||||||
|
|
||||||
|
// i'm making a separate constant for vsc to help me
|
||||||
|
const body: LoginReponseBody = response.body
|
||||||
|
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(body.uid).not.toBe(undefined)
|
||||||
|
expect(body.encryption).toBe(defaultEncryptionMethod)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
import Router from 'koa-router'
|
import Router from 'koa-router'
|
||||||
|
import { validate } from '../../../common/validation/middleware/validate'
|
||||||
|
import { getPasswordByEmail } from '../queries/getPasswordByEmail'
|
||||||
|
import { HttpError } from '../../network/classes/HttpError'
|
||||||
|
import { checkPassword } from '../helpers/checkPassword'
|
||||||
|
import { SignupBodySchema } from '../schemas/SignupBody'
|
||||||
|
import { encryptPassword } from '../helpers/encryptPassword'
|
||||||
|
import { createAccount } from '../queries/createAccount'
|
||||||
|
import { defaultEncryptionMethod } from '../constants'
|
||||||
|
import { LoginBodySchema } from '../schemas/LoginBody'
|
||||||
|
import { isUnauthorized } from '../middleware/isUnauthorized'
|
||||||
|
|
||||||
const router = new Router()
|
const router = new Router()
|
||||||
|
|
||||||
|
@ -10,11 +20,65 @@ router.get('/', (context, next) => {
|
||||||
return next()
|
return next()
|
||||||
})
|
})
|
||||||
|
|
||||||
router.post('/login', (context, next) => {
|
router.post(
|
||||||
context.session.uid = context.request.body.uid
|
'/login',
|
||||||
context.body = {}
|
isUnauthorized(),
|
||||||
|
validate(LoginBodySchema, 'body'),
|
||||||
|
async (context, next) => {
|
||||||
|
const { email, password } = context.request.body
|
||||||
|
|
||||||
return next()
|
const passwordData = await getPasswordByEmail(email)
|
||||||
})
|
|
||||||
|
// in case the user doesnt exist
|
||||||
|
if (!passwordData) {
|
||||||
|
throw new HttpError(400)
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = checkPassword(
|
||||||
|
passwordData.password,
|
||||||
|
password,
|
||||||
|
passwordData.passwordEncryption
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
throw new HttpError(400, 'wrong password')
|
||||||
|
}
|
||||||
|
|
||||||
|
context.session.uid = passwordData.id
|
||||||
|
|
||||||
|
context.body = {
|
||||||
|
encryption: passwordData.passwordEncryption,
|
||||||
|
uid: passwordData.id
|
||||||
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/signup',
|
||||||
|
isUnauthorized(),
|
||||||
|
validate(SignupBodySchema, 'body'),
|
||||||
|
async (context, next) => {
|
||||||
|
const { email, name, password } = context.request.body
|
||||||
|
|
||||||
|
// encript the password (bcrypt by default)
|
||||||
|
const encryptedPassword = await encryptPassword(password, defaultEncryptionMethod, 10)
|
||||||
|
|
||||||
|
const uid = await createAccount({
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
password: encryptedPassword,
|
||||||
|
passwordEncryption: defaultEncryptionMethod
|
||||||
|
})
|
||||||
|
|
||||||
|
context.body = {
|
||||||
|
uid,
|
||||||
|
encryption: defaultEncryptionMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import Joi from 'joi'
|
import Joi from '@hapi/joi'
|
||||||
import { name, password } from './authFields'
|
import { email, password } from './authFields'
|
||||||
|
|
||||||
export const LoginBodySchema = Joi.object({
|
export const LoginBodySchema = Joi.object({
|
||||||
name,
|
email,
|
||||||
password
|
password
|
||||||
})
|
}).required()
|
||||||
|
|
||||||
|
export type LoginBody = Joi.extractType<typeof LoginBodySchema>
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import Joi from '@hapi/joi'
|
||||||
|
import { email, name, password } from './authFields'
|
||||||
|
|
||||||
|
export const SignupBodySchema = Joi.object({
|
||||||
|
name,
|
||||||
|
password,
|
||||||
|
email
|
||||||
|
}).required()
|
||||||
|
|
||||||
|
export type SignupBody = Joi.extractType<typeof SignupBodySchema>
|
|
@ -1,10 +1,8 @@
|
||||||
import Joi from 'joi'
|
import Joi from 'joi'
|
||||||
|
|
||||||
export const name = Joi.string()
|
export const name = Joi.string()
|
||||||
.alphanum()
|
|
||||||
.min(3)
|
.min(3)
|
||||||
.max(30)
|
.max(30)
|
||||||
.lowercase()
|
|
||||||
.required()
|
.required()
|
||||||
|
|
||||||
export const email = Joi.string()
|
export const email = Joi.string()
|
||||||
|
@ -15,6 +13,6 @@ export const email = Joi.string()
|
||||||
|
|
||||||
export const password = Joi.string()
|
export const password = Joi.string()
|
||||||
.min(3)
|
.min(3)
|
||||||
.max(50)
|
.max(20)
|
||||||
.alphanum()
|
.alphanum()
|
||||||
.required()
|
.required()
|
||||||
|
|
32
typescript/lunargame/api/src/modules/auth/types/Account.ts
Normal file
32
typescript/lunargame/api/src/modules/auth/types/Account.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { passwordEncryption } from './passwordEncryption'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data about an account wich needs to be inserted into the db
|
||||||
|
*/
|
||||||
|
export interface DbAccount {
|
||||||
|
name: string
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
passwordEncryption: passwordEncryption
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data about an account wich actually gets stored into the db
|
||||||
|
*/
|
||||||
|
export interface FullDbAccount extends DbAccount {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data everyone can get about an account
|
||||||
|
*/
|
||||||
|
export interface AccountPublicData {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data only the owner of the account has acces to
|
||||||
|
*/
|
||||||
|
export interface AccountPrivateData extends AccountPublicData {
|
||||||
|
email: string
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { passwordEncryption } from './passwordEncryption'
|
||||||
|
|
||||||
|
export interface LoginReponseBody {
|
||||||
|
uid: number
|
||||||
|
encryption: passwordEncryption
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* All modes a password can be encrypted in
|
||||||
|
*/
|
||||||
|
export type passwordEncryption = 'plain' | 'bcrypt'
|
21
typescript/lunargame/api/test/seeds/01_create-account.ts
Normal file
21
typescript/lunargame/api/test/seeds/01_create-account.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import * as Knex from 'knex'
|
||||||
|
import { DbAccount } from '../../src/modules/auth/types/Account'
|
||||||
|
|
||||||
|
const tableName = 'account'
|
||||||
|
|
||||||
|
export const mockAccounts: DbAccount[] = [
|
||||||
|
{
|
||||||
|
name: 'Adriel',
|
||||||
|
email: 'rafaeladriel11@gmail.com',
|
||||||
|
password: '1234',
|
||||||
|
passwordEncryption: 'plain'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export async function seed(knex: Knex): Promise<any> {
|
||||||
|
return knex(tableName)
|
||||||
|
.del()
|
||||||
|
.then(() => {
|
||||||
|
return knex(tableName).insert(mockAccounts)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
import supertest from 'supertest'
|
import supertest from 'supertest'
|
||||||
|
import 'joi-extract-type'
|
||||||
|
import { LoginBody } from '../../src/modules/auth/schemas/LoginBody'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to get a supertest agent wich is logged in
|
* Helper to get a supertest agent wich is logged in
|
||||||
|
@ -8,10 +10,11 @@ import supertest from 'supertest'
|
||||||
*/
|
*/
|
||||||
export const loggedInAgent = async (
|
export const loggedInAgent = async (
|
||||||
agent: supertest.SuperTest<supertest.Test>,
|
agent: supertest.SuperTest<supertest.Test>,
|
||||||
uid: number
|
{ email, password }: LoginBody
|
||||||
) => {
|
) => {
|
||||||
const response = await agent.post('/auth/login').send({
|
const response = await agent.post('/auth/login').send({
|
||||||
uid
|
email,
|
||||||
|
password
|
||||||
})
|
})
|
||||||
|
|
||||||
// the cookie to send back
|
// the cookie to send back
|
||||||
|
|
Loading…
Reference in a new issue