Skip to content

Commit

Permalink
Implement token-based auth API
Browse files Browse the repository at this point in the history
  • Loading branch information
tobyzerner committed Jan 22, 2015
1 parent 74c9b48 commit ad269fd
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 44 deletions.
32 changes: 32 additions & 0 deletions src/Flarum/Api/Actions/Auth/Login.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php namespace Flarum\Api\Actions\Auth;

use Event;
use Response;
use Auth;

use Flarum\Core\Users\User;
use Flarum\Api\Actions\Base;

class Login extends Base
{
/**
* Log in and return a token.
*
* @return Response
*/
protected function run()
{
$identifier = $this->input('identifier');
$password = $this->input('password');
$field = filter_var($identifier, FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
$credentials = [$field => $identifier, 'password' => $password];

if (! Auth::attempt($credentials)) {
return $this->respondWithError('invalidLogin', 401);
}

$token = Auth::user()->getRememberToken();

return Response::json(compact('token'));
}
}
2 changes: 2 additions & 0 deletions src/Flarum/Core/CoreServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public function boot()

$this->app->make('validator')->extend('username', 'Flarum\Core\Users\UsernameValidator@validate');

$this->app['config']->set('auth.model', 'Flarum\Core\Users\User');

Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\DiscussionMetadataUpdater');
Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\UserMetadataUpdater');
Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\PostFormatter');
Expand Down
2 changes: 1 addition & 1 deletion src/Flarum/Core/Discussions/Discussion.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static function boot()
// Allow a user to edit their own discussion.
static::grant('edit', function ($grant, $user) {
if (app('flarum.permissions')->granted($user, 'editOwn', 'discussion')) {
$grant->where('user_id', $user->id);
$grant->where('start_user_id', $user->id);
}
});

Expand Down
36 changes: 5 additions & 31 deletions src/Flarum/Core/Users/User.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<?php namespace Flarum\Core\Users;

use Illuminate\Auth\UserInterface;
use Illuminate\Auth\UserTrait;
use Illuminate\Auth\Reminders\RemindableInterface;
use Illuminate\Auth\Reminders\RemindableTrait;
use Auth;
use DB;
use Event;
Expand All @@ -13,10 +15,12 @@
use Flarum\Core\Groups\Group;
use Flarum\Core\Support\Exceptions\PermissionDeniedException;

class User extends Entity /*implements UserInterface, RemindableInterface*/
class User extends Entity implements UserInterface, RemindableInterface
{
use EventGenerator;
use Permissible;

use UserTrait, RemindableTrait;

protected static $rules = [
'username' => 'required|username|unique',
Expand Down Expand Up @@ -206,34 +210,4 @@ public function activity()
{
return $this->hasMany('Flarum\Core\Activity\Activity');
}

/**
* Get the unique identifier for the user.
*
* @return mixed
*/
public function getAuthIdentifier()
{
return $this->getKey();
}

/**
* Get the password for the user.
*
* @return string
*/
public function getAuthPassword()
{
return $this->password;
}

/**
* Get the e-mail address where password reminders are sent.
*
* @return string
*/
public function getReminderEmail()
{
return $this->email;
}
}
1 change: 1 addition & 0 deletions src/migrations/2014_01_14_231404_create_users_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public function up()
$table->string('username');
$table->string('email');
$table->string('password');
$table->rememberToken();
$table->dateTime('join_time');
$table->string('time_zone');
$table->dateTime('last_seen_time')->nullable();
Expand Down
25 changes: 23 additions & 2 deletions src/routes.api.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,35 @@
$action = function($class)
{
return function () use ($class) {
$action = \App::make($class);
$action = App::make($class);
$request = app('request');
$parameters = Route::current()->parameters();
return $action->handle($request, $parameters);
};
};

Route::group(['prefix' => 'api'], function () use ($action) {
// @todo refactor into a unit-testable class
Route::filter('attemptLogin', function($route, $request) {
$prefix = 'Token ';
if (starts_with($request->headers->get('authorization'), $prefix)) {
$token = substr($request->headers->get('authorization'), strlen($prefix));
Auth::once(['remember_token' => $token]);
}
});

Route::group(['prefix' => 'api', 'before' => 'attemptLogin'], function () use ($action) {

/*
|--------------------------------------------------------------------------
| Auth
|--------------------------------------------------------------------------
*/

// Login
Route::post('auth/login', [
'as' => 'flarum.api.auth.login',
'uses' => $action('Flarum\Api\Actions\Auth\Login')
]);

/*
|--------------------------------------------------------------------------
Expand Down
29 changes: 27 additions & 2 deletions tests/_support/ApiHelper.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
<?php
namespace Codeception\Module;

// here you can define custom actions
// all public methods declared in helper class will be available in $I
use Laracasts\TestDummy\Factory;
use Auth;
use DB;

class ApiHelper extends \Codeception\Module
{
public function haveAnAccount($data = [])
{
return Factory::create('Flarum\Core\Users\User', $data);
}

public function login($identifier, $password)
{
$this->getModule('REST')->sendPOST('/api/auth/login', ['identifier' => $identifier, 'password' => $password]);

$response = json_decode($this->getModule('REST')->grabResponse(), true);
if ($response && is_array($response) && isset($response['token'])) {
return $response['token'];
}

return false;
}

public function amAuthenticated()
{
$user = $this->haveAnAccount();
$user->groups()->attach(3); // Add member group
Auth::onceUsingId($user->id);

return $user;
}
}
35 changes: 34 additions & 1 deletion tests/api/ApiTester.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php //[STAMP] 56e5f4700a805fa943ff8199ddb69b69
<?php //[STAMP] 93c972ae47d60c70b9045d971476f0bc

// This class was automatically generated by build task
// You should not change it manually as it will be overwritten on next build
Expand Down Expand Up @@ -3029,4 +3029,37 @@ public function assertFalse($condition, $message = null) {
public function fail($message) {
return $this->scenario->runStep(new \Codeception\Step\Action('fail', func_get_args()));
}


/**
* [!] Method is generated. Documentation taken from corresponding module.
*
*
* @see \Codeception\Module\ApiHelper::haveAnAccount()
*/
public function haveAnAccount($data = null) {
return $this->scenario->runStep(new \Codeception\Step\Action('haveAnAccount', func_get_args()));
}


/**
* [!] Method is generated. Documentation taken from corresponding module.
*
*
* @see \Codeception\Module\ApiHelper::login()
*/
public function login($identifier, $password) {
return $this->scenario->runStep(new \Codeception\Step\Action('login', func_get_args()));
}


/**
* [!] Method is generated. Documentation taken from corresponding module.
*
*
* @see \Codeception\Module\ApiHelper::amAuthenticated()
*/
public function amAuthenticated() {
return $this->scenario->runStep(new \Codeception\Step\Condition('amAuthenticated', func_get_args()));
}
}
55 changes: 55 additions & 0 deletions tests/api/AuthCest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php
use \ApiTester;

use Laracasts\TestDummy\Factory;

class AuthCest
{
protected $endpoint = '/api/auth';

public function loginWithEmail(ApiTester $I)
{
$I->wantTo('login via API with email');

$user = $I->haveAnAccount([
'email' => 'foo@bar.com',
'password' => 'pass7word'
]);

$token = $I->login('foo@bar.com', 'pass7word');
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();

$loggedIn = User::where('remember_token', $token)->first();
$I->assertEquals($user->id, $loggedIn->id);
}

public function loginWithUsername(ApiTester $I)
{
$I->wantTo('login via API with username');

$user = $I->haveAnAccount([
'username' => 'tobscure',
'password' => 'pass7word'
]);

$token = $I->login('tobscure', 'pass7word');
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();

$loggedIn = User::where('remember_token', $token)->first();
$I->assertEquals($user->id, $loggedIn->id);
}

public function invalidLogin(ApiTester $I)
{
$user = $I->haveAnAccount([
'email' => 'foo@bar.com',
'password' => 'pass7word'
]);

$I->login('foo@bar.com', 'incorrect');
$I->seeResponseCodeIs(401);
$I->seeResponseIsJson();
}
}
11 changes: 6 additions & 5 deletions tests/api/DiscussionsResourceCest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function createDiscussion(ApiTester $I)
{
$I->wantTo('create a discussion via API');

$I->haveHttpHeader('Authorization', 'Token 123456');
$I->amAuthenticated();

$I->sendPOST($this->endpoint, ['discussions' => ['title' => 'foo', 'content' => 'bar']]);
$I->seeResponseCodeIs(200);
Expand All @@ -58,9 +58,9 @@ public function updateDiscussion(ApiTester $I)
{
$I->wantTo('update a discussion via API');

$I->haveHttpHeader('Authorization', 'Token 123456');
$user = $I->amAuthenticated();

$discussion = Factory::create('Flarum\Core\Discussions\Discussion');
$discussion = Factory::create('Flarum\Core\Discussions\Discussion', ['start_user_id' => $user->id]);

$I->sendPUT($this->endpoint.'/'.$discussion->id, ['discussions' => ['title' => 'foo']]);
$I->seeResponseCodeIs(200);
Expand All @@ -75,9 +75,10 @@ public function deleteDiscussion(ApiTester $I)
{
$I->wantTo('delete a discussion via API');

$I->haveHttpHeader('Authorization', 'Token 123456');
$user = $I->amAuthenticated();
$user->groups()->attach(4);

$discussion = Factory::create('Flarum\Core\Discussions\Discussion');
$discussion = Factory::create('Flarum\Core\Discussions\Discussion', ['start_user_id' => $user->id]);

$I->sendDELETE($this->endpoint.'/'.$discussion->id);
$I->seeResponseCodeIs(204);
Expand Down
10 changes: 8 additions & 2 deletions tests/factories/factories.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
<?php

$factory('Flarum\Core\Discussions\Discussion', [
'title' => $faker->sentence
'title' => $faker->sentence,
'start_time' => $faker->dateTimeThisYear,
'start_user_id' => 'factory:Flarum\Core\Users\User'
]);

$factory('Flarum\Core\Users\User', [
'username' => $faker->sentence
'username' => $faker->userName,
'email' => $faker->safeEmail,
'password' => 'password',
'join_time' => $faker->dateTimeThisYear,
'time_zone' => $faker->timezone
]);

0 comments on commit ad269fd

Please sign in to comment.