Skip to content

Commit

Permalink
feat(user): auth + create user
Browse files Browse the repository at this point in the history
  • Loading branch information
RomainLanz committed Jan 16, 2018
1 parent a42624c commit 0a40ee5
Show file tree
Hide file tree
Showing 18 changed files with 591 additions and 18 deletions.
4 changes: 0 additions & 4 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# editorconfig.org
root = true

[*]
Expand All @@ -8,6 +7,3 @@ end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
10 changes: 10 additions & 0 deletions .env.testing
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HOST=127.0.0.1
PORT=4000
NODE_ENV=testing

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=api.lausanne-esports.ch-test
23 changes: 23 additions & 0 deletions app/Controllers/Http/SessionController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict'

const User = use('App/Models/User')
const InvalidCredential = use('App/Exceptions/InvalidCredentialException')

class SessionController {
async store ({ auth, request, response }) {
const { email, password } = request.all()

const token = await auth.attempt(email, password)
.catch((e) => {
throw new InvalidCredential('Authentication failed. Either supplied credentials are invalid or the account is inactive', 401, 'E_INVALID_CREDENTIAL')
})

return response.ok({
token,
status: 200,
message: 'Logged in successfully',
})
}
}

module.exports = SessionController
23 changes: 23 additions & 0 deletions app/Controllers/Http/UserController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict'

const User = use('App/Models/User')

class UserController {
async store ({ request, response }) {
const data = request.only([
'username', 'email', 'password', 'password_confirmation']
)

delete data.password_confirmation

const user = await User.create(data)

return response.ok({
user,
status: 200,
message: 'Account created successfully',
})
}
}

module.exports = UserController
45 changes: 45 additions & 0 deletions app/Exceptions/Handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use strict'

/**
* This class handles all exceptions thrown during
* the HTTP request lifecycle.
*
* @class ExceptionHandler
*/
class ExceptionHandler {
/**
* Handle exception thrown during the HTTP lifecycle
*
* @method handle
*
* @param {Object} error
* @param {Object} options.request
* @param {Object} options.response
*
* @return {void}
*/
async handle (error, { request, response }) {
response.status(error.status).send({
errors: [{
"status": error.status,
"code": error.code,
"detail": error.message,
}]
})
}

/**
* Report exception for logging or debugging.
*
* @method report
*
* @param {Object} error
* @param {Object} options.request
*
* @return {void}
*/
async report (error, { request }) {
}
}

module.exports = ExceptionHandler
12 changes: 12 additions & 0 deletions app/Exceptions/InvalidCredentialException.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict'

const { LogicalException } = require('@adonisjs/generic-exceptions')

class InvalidCredentialException extends LogicalException {
/**
* Handle this exception by itself
*/
// handle () {}
}

module.exports = InvalidCredentialException
15 changes: 15 additions & 0 deletions app/Middleware/AcceptFormatGate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict'

class AcceptFormatGate {
async handle ({ request }, next) {
// console.log(request.request.headers.accept)
// if (!request.accepts(['html', 'json']) === 'json') {
request.request.headers.accept = 'application/json'
// }

// call next to advance the request
await next()
}
}

module.exports = AcceptFormatGate
4 changes: 4 additions & 0 deletions app/Models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class User extends Model {
this.addHook('beforeCreate', 'User.hashPassword')
}

static get hidden () {
return ['password']
}

/**
* A relationship on tokens is required for auth to
* work. Since features like `refreshTokens` or
Expand Down
20 changes: 20 additions & 0 deletions app/Validators/SessionStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict'

class SessionStore {
get formatter () {
return 'jsonapi'
}

get validateAll () {
return true
}

get rules () {
return {
email: 'required|email',
password: 'required',
}
}
}

module.exports = SessionStore
22 changes: 22 additions & 0 deletions app/Validators/UserStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict'

class UserStore {
get formatter () {
return 'jsonapi'
}

get validateAll () {
return true
}

get rules () {
return {
username: 'required|unique:users',
email: 'required|email|unique:users',
password: 'required',
password_confirmation: 'required_if:password|same:password',
}
}
}

module.exports = UserStore
18 changes: 10 additions & 8 deletions database/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
|
*/

// const Factory = use('Factory')
const Factory = use('Factory')

/**
Factory.blueprint('App/Models/User', (faker) => {
return {
username: faker.username()
}
})
*/
Factory.blueprint('App/Models/User', (faker, index, data) => {
const defaultValue = {
username: faker.username(),
email: faker.email(),
password: 'secret',
}

return Object.assign(defaultValue, data)
})
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@
"@adonisjs/fold": "^4.0.5",
"@adonisjs/framework": "^4.0.28",
"@adonisjs/ignitor": "^1.0.14",
"@adonisjs/lucid": "^4.0.24"
"@adonisjs/lucid": "^4.0.24",
"@adonisjs/validator": "^4.0.8",
"@adonisjs/vow": "^1.0.13",
"global": "^4.3.2",
"mysql": "^2.15.0",
"now": "^9.0.1"
},
"devDependencies": {},
"autoload": {
Expand Down
4 changes: 3 additions & 1 deletion start/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ const providers = [
'@adonisjs/auth/providers/AuthProvider',
'@adonisjs/bodyparser/providers/BodyParserProvider',
'@adonisjs/cors/providers/CorsProvider',
'@adonisjs/lucid/providers/LucidProvider'
'@adonisjs/lucid/providers/LucidProvider',
'@adonisjs/validator/providers/ValidatorProvider',
'@adonisjs/vow/providers/VowProvider',
]

/*
Expand Down
3 changes: 2 additions & 1 deletion start/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ const Server = use('Server')
|
*/
const globalMiddleware = [
'Adonis/Middleware/BodyParser'
'Adonis/Middleware/BodyParser',
'App/Middleware/AcceptFormatGate',
]

/*
Expand Down
5 changes: 2 additions & 3 deletions start/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,5 @@

const Route = use('Route')

Route.get('/', ({ request }) => {
return { greeting: 'Hello world in JSON' }
})
Route.post('/users', 'UserController.store').validator('UserStore')
Route.post('/sessions', 'SessionController.store').validator('SessionStore')
130 changes: 130 additions & 0 deletions test/functional/session-store.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
'use strict'

const Factory = use('Factory')
const { test, trait } = use('Test/Suite')('Session Store')

trait('Test/ApiClient')
trait('DatabaseTransactions')

test('should be able to signin', async ({ assert, client }) => {
const user = await Factory.model('App/Models/User').create()

const response = await client
.post('sessions')
.send({
email: user.email,
password: 'secret',
})
.end()

response.assertStatus(200)
response.assertJSONSubset({
message: 'Logged in successfully',
})
})

test('should throw invalid credential when email is incorrect', async ({ assert, client }) => {
const response = await client
.post('sessions')
.send({
email: 'romain.lanz@lausanne-esports.ch',
password: 'secret',
})
.end()

response.assertStatus(401)
response.assertJSONSubset({
errors: [{
status: 401,
code: 'E_INVALID_CREDENTIAL',
detail: 'E_INVALID_CREDENTIAL: Authentication failed. Either supplied credentials are invalid or the account is inactive',
}]
})
})

test('should throw invalid credential when password is incorrect', async ({ assert, client }) => {
const user = await Factory.model('App/Models/User').create()

const response = await client
.post('sessions')
.send({
email: user.email,
password: 'ThisIsntGoingToWork',
})
.end()

response.assertStatus(401)
response.assertJSONSubset({
errors: [{
status: 401,
code: 'E_INVALID_CREDENTIAL',
detail: 'E_INVALID_CREDENTIAL: Authentication failed. Either supplied credentials are invalid or the account is inactive',
}]
})
})

test('should test that email is required', async ({ assert, client }) => {
const response = await client
.post('sessions')
.send({
password: 'ThisIsntGoingToWork'
})
.end()

response.assertStatus(400)
response.assertJSONSubset({
errors: [{
source: { pointer: 'email' },
title: 'required',
}]
})
})

test('should test that password is required', async ({ assert, client }) => {
const response = await client
.post('sessions')
.send({
email: 'romain.lanz@lausanne-esports.ch',
})
.end()

response.assertStatus(400)
response.assertJSONSubset({
errors: [{
source: { pointer: 'password' },
title: 'required',
}]
})
})

test('should test that email must be correctly formated', async ({ assert, client }) => {
const response = await client
.post('sessions')
.send({
email: 'ThisIsntGoingToWork'
})
.end()

response.assertStatus(400)
response.assertJSONSubset({
errors: [{
source: { pointer: 'email' },
title: 'email',
}]
})
})

test('should test that all errors are sent back', async ({ assert, client }) => {
const response = await client
.post('sessions')
.send({})
.end()

response.assertStatus(400)
response.assertJSONSubset({
errors: [
{ source: { pointer: 'email' }, title: 'required' },
{ source: { pointer: 'password' }, title: 'required' },
]
})
})
Loading

0 comments on commit 0a40ee5

Please sign in to comment.