Refactor to refresh token on each call to API. Fixes #514

This commit is contained in:
Matthew Ross 2020-08-19 19:40:04 -04:00
parent 44a52cd3b7
commit 184cb08960
8 changed files with 26 additions and 112 deletions

View File

@ -217,48 +217,15 @@ class Auth extends BaseController {
return $this->jsonResponse($response);
}
public function refreshToken($request, $response) {
$response = self::ValidateToken($request, $response);
$status = $response->getStatusCode();
if ($status !== 200) {
if ($status === 400) {
$this->apiJson->addAlert('error',
'Authorization header missing.');
return $this->jsonResponse($response, $status);
}
$this->apiJson->addAlert('error', 'Invalid API token.');
return $this->jsonResponse($response, $status);
}
$jwt = $request->getHeader('Authorization')[0];
$payload = self::getJwtPayload($jwt);
$user = R::load('user', $payload->uid);
$jwt = self::createJwt($user->id, (int)$payload->mul);
$user->active_token = $jwt;
R::store($user);
$opts = R::load('useroption', $user->user_option_id);
$this->apiJson->setSuccess();
$this->apiJson->addData($jwt);
$this->apiJson->addData($this->sanitizeUser($user));
$this->apiJson->addData($opts);
return $this->jsonResponse($response);
public static function createJwt($userId, $mult = 1) {
return JWT::encode(array(
'exp' => time() + (60 * 30) * $mult, // 30 minutes * $mult
'uid' => (int)$userId,
'mul' => $mult
), Auth::getJwtKey());
}
private function sanitizeUser($user) {
unset($user->password_hash);
unset($user->active_token);
return $user;
}
private static function getJwtPayload($jwt) {
public static function getJwtPayload($jwt) {
try {
$payload = JWT::decode($jwt, self::getJwtKey(), ['HS256']);
} catch (Exception $ex) {
@ -268,16 +235,11 @@ class Auth extends BaseController {
return $payload;
}
private static function createJwt($userId, $mult = 1) {
// If 'remember me' feature is desired, set the multiplier higher.
// By default, a token will expire after half an hour, but can be
// refreshed by a call to /api/refresh.
private function sanitizeUser($user) {
unset($user->password_hash);
unset($user->active_token);
return JWT::encode(array(
'exp' => time() + (60 * 30) * $mult, // 30 minutes * $mult
'uid' => (int)$userId,
'mul' => $mult
), Auth::getJwtKey());
return $user;
}
private static function getJwtKey() {

View File

@ -67,8 +67,13 @@ abstract class BaseController {
return 403;
}
$payload = Auth::getJwtPayload($request->getHeader('Authorization')[0]);
$user->active_token = Auth::createJwt($user->id, $payload->mul);
R::store($user);
$this->setStrings($user->userOptionId);
$this->apiJson->addData($request->getHeader('Authorization'));
$this->apiJson->addData($user->active_token);
return $status;
}

View File

@ -104,7 +104,6 @@ $app->get('/activity[/{type}[/{id}]]', 'Activity:getActivity'); // BoardAdmin (w
$app->post('/login', 'Auth:login'); // Unsecured (creates JWT)
$app->post('/logout', 'Auth:logout'); // Unsecured (clears JWT)
$app->post('/authenticate', 'Auth:authenticate'); // Unsecured (checks JWT)
$app->post('/refresh', 'Auth:refreshToken'); // Unsecured (checks and updates JWT)
$app->run();
R::close();

View File

@ -75,9 +75,8 @@
<h1>{{ strings['boards_noDefault'] }}</h1>
<p>{{ strings['boards_noDefaultMessage'] }}
<a href="javascript:" [routerLink]="['/settings']">
{{ strings['settings'] }}
</a>.
<a href="javascript:"
[routerLink]="['/settings']">{{ strings['settings'] }}</a>.
</p>
<p></p>

View File

@ -145,17 +145,15 @@ export class BoardDisplayComponent implements OnInit, OnDestroy {
}
updateBoards(): void {
this.boardService.refreshToken(() => {
this.boardService.getBoards().subscribe((response: ApiResponse) => {
this.boards = [];
this.boardService.getBoards().subscribe((response: ApiResponse) => {
this.boards = [];
if (response.data.length > 1) {
this.updateBoardsList(response.data[1]);
return;
}
if (response.data.length > 1) {
this.updateBoardsList(response.data[1]);
return;
}
this.loading = false;
});
this.loading = false;
});
}

View File

@ -180,10 +180,6 @@ export class BoardService extends ApiService {
)
}
refreshToken(callback: any): void {
this.http.post(this.apiBase + 'refresh', {}).subscribe(() => callback());
}
private async convertBoardData(boardData: any): Promise<Board> {
if (boardData instanceof Board) {
return boardData;

View File

@ -232,45 +232,5 @@ class AuthTest extends PHPUnit\Framework\TestCase {
$this->assertEquals('failure', $actual->body->data->status);
}
public function testRefreshToken() {
$data = new stdClass();
$data->username = 'admin';
$data->password = 'admin';
$data->remember = false;
$request = new RequestMock();
$request->payload = $data;
Auth::CreateInitialAdmin(new LoggerMock());
Auth::CreateJwtSigningKey();
$actual = $this->auth->login($request, new ResponseMock(), null);
$this->assertEquals('success', $actual->body->data->status);
$jwt = $actual->body->data->data[0];
$this->auth = new Auth(new LoggerMock());
$request = new RequestMock();
$request->header = [$jwt];
$actual = $this->auth->refreshToken($request, new ResponseMock(), null);
$user = R::load('user', 1);
$this->assertEquals('success', $actual->body->data->status);
$this->assertEquals($user->active_token, $actual->body->data->data[0]);
$this->auth = new Auth(new LoggerMock());
$request->hasHeader = false;
$actual = $this->auth->refreshToken($request, new ResponseMock(), null);
$this->assertEquals('failure', $actual->body->data->status);
$this->auth = new Auth(new LoggerMock());
$request = new RequestMock();
$request->header = ['not a valid JWT'];
$actual = $this->auth->refreshToken($request, new ResponseMock(), null);
$this->assertEquals('failure', $actual->body->data->status);
}
}

View File

@ -287,10 +287,5 @@ describe('BoardService', () => {
testCall('api/upload/asdf', 'POST', true);
});
it('refreshes the API token', () => {
service.refreshToken(() => {});
testCall('api/refresh', 'POST');
});
});