mirror of
https://github.com/flarum/framework.git
synced 2025-02-21 04:47:08 +08:00
Implement token-based auth API
This commit is contained in:
parent
28d8718e27
commit
215bdb672a
32
framework/core/src/Flarum/Api/Actions/Auth/Login.php
Normal file
32
framework/core/src/Flarum/Api/Actions/Auth/Login.php
Normal file
@ -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'));
|
||||
}
|
||||
}
|
@ -24,6 +24,8 @@ class CoreServiceProvider extends ServiceProvider
|
||||
|
||||
$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');
|
||||
|
@ -44,7 +44,7 @@ class Discussion extends Entity
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
@ -13,10 +15,12 @@ use Flarum\Core\Entity;
|
||||
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',
|
||||
@ -206,34 +210,4 @@ class User extends Entity /*implements UserInterface, RemindableInterface*/
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ class CreateUsersTable extends Migration {
|
||||
$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();
|
||||
|
@ -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')
|
||||
]);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -3029,4 +3029,37 @@ class ApiTester extends \Codeception\Actor
|
||||
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
framework/core/tests/api/AuthCest.php
Normal file
55
framework/core/tests/api/AuthCest.php
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -42,7 +42,7 @@ class DiscussionsResourceCest {
|
||||
{
|
||||
$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);
|
||||
@ -58,9 +58,9 @@ class DiscussionsResourceCest {
|
||||
{
|
||||
$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);
|
||||
@ -75,9 +75,10 @@ class DiscussionsResourceCest {
|
||||
{
|
||||
$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);
|
||||
|
@ -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
|
||||
]);
|
Loading…
x
Reference in New Issue
Block a user