Commit b86f1216 authored by Jacob Priddy's avatar Jacob Priddy 👌
Browse files

Merge branch '2-integrate-api-auth' into 'master'

Resolve "Integrate API Auth"

Closes #2

See merge request kretschmar/doorcode!7
parents fa850cb9 e7d604a9
Pipeline #1584 passed with stages
in 1 minute and 36 seconds
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
"${DIR}/../secrets/gen-certs.sh"
docker-compose -f "${DIR}/../docker-compose.yml" up -d
docker-compose -f "${DIR}/../docker-compose.yml" exec api ./install-dev.sh
version: '3'
version: '3.7'
networks:
doorcode:
driver: bridge
secrets:
webserver_cert:
file: ./secrets/certs/webserver.cert
webserver_key:
file: ./secrets/certs/webserver.key
volumes:
db-data:
services:
webserver:
image: nginx:1-alpine
container_name: webserver
restart: unless-stopped
tty: true
secrets:
- webserver_cert
- webserver_key
ports:
- "8080:80"
- "8080:443"
volumes:
- ./src/web:/var/www
- ./nginx/conf.d/:/etc/nginx/conf.d/
......@@ -27,12 +39,14 @@ services:
networks:
- doorcode
postgres:
image: postgres:12-alpine
image: postgres:11-alpine
container_name: postgres
restart: unless-stopped
tty: true
ports:
- "5432:5432"
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: web
POSTGRES_PASSWORD: secret
......@@ -46,6 +60,8 @@ services:
tty: true
ports:
- "8081:80"
depends_on:
- postgres
environment:
PHP_PG_ADMIN_SERVER_HOST: postgres
PHP_PG_ADMIN_SERVER_DEFAULT_DB: doorcode
......
# Thers a stupid bug in nginx that's been around for years that makes it so we can't easily put both front and backend
# on the same server. So we'll just do a proxy pass...
upstream localhost.api {
server 127.0.0.1;
server 127.0.0.1:443;
}
server {
listen 80;
listen 443 ssl;
index index.php index.html;
server_name localhost;
ssl_certificate /run/secrets/webserver_cert;
ssl_certificate_key /run/secrets/webserver_key;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /var/www/frontend;
......@@ -38,7 +42,7 @@ server {
}
location /api {
proxy_pass http://localhost.api/;
proxy_pass https://localhost.api/;
}
location / {
......@@ -48,9 +52,13 @@ server {
server {
listen 80;
listen 443 ssl;
index index.php index.html;
server_name localhost.api;
ssl_certificate /run/secrets/webserver_cert;
ssl_certificate_key /run/secrets/webserver_key;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /var/www/backend/public;
......
*
!.gitignore
\ No newline at end of file
#!/bin/bash
echo "Generating cert for domain: $1"
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \
-subj "/C=US/ST=Washington/L=College Place/O=WWU/CN=$1" \
-keyout "${DIR}/certs/webserver.key" -out "${DIR}/certs/webserver.cert"
......@@ -27,7 +27,7 @@ $config = [
* external url, no matter where you come from (direct access or via the
* reverse proxy).
*/
'baseurlpath' => 'http://localhost:8080/simplesaml/',
'baseurlpath' => 'https://localhost:8080/simplesaml/',
/*
* The 'application' configuration array groups a set configuration options
......
<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Door extends Authenticatable {
protected $fillable = [
'*'
];
}
......@@ -6,7 +6,9 @@ use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Source\Exceptions\EntityExistsException;
use Illuminate\Auth\AuthenticationException;
use Source\Exceptions\EntityNotFoundException;
use Illuminate\Validation\ValidationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler {
......@@ -54,4 +56,30 @@ class Handler extends ExceptionHandler {
return parent::render($request, $exception);
}
/**
* Convert an authentication exception into a response.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Auth\AuthenticationException $exception
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function unauthenticated($request, AuthenticationException $exception) {
return response()->json(['message' => $exception->getMessage()], 401);
}
/**
* Create a response object from the given validation exception.
*
* @param \Illuminate\Validation\ValidationException $e
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function convertValidationExceptionToResponse(ValidationException $e, $request) {
if ($e->response) {
return $e->response;
}
return $this->invalidJson($request, $e);
}
}
<?php
namespace App\Guards;
use Illuminate\Http\Request;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\Authenticatable;
use Source\UseCases\Token\Authenticate\AuthenticateUseCase;
use Source\UseCases\Token\Authenticate\TranslationPresenter;
class ApiGuard implements Guard {
use GuardHelpers;
/**
* The request instance.
*
* @var Request
*/
protected Request $request;
/**
* The name of the query string item from the request containing the API token.
*
* @var string
*/
protected string $inputKey;
/**
* @var AuthenticateUseCase
*/
protected AuthenticateUseCase $authenticator;
/**
* Create a new authentication guard.
*
* @param AuthenticateUseCase $authenticator
* @param Request $request
* @param string $inputKey
*/
public function __construct(
AuthenticateUseCase $authenticator,
Request $request,
$inputKey = 'api_token'){
$this->request = $request;
$this->inputKey = $inputKey;
$this->authenticator = $authenticator;
}
/**
* Get the currently authenticated user.
*
* @return Authenticatable|null
*/
public function user() {
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if ($this->user !== null) {
return $this->user;
}
$user = null;
$token = $this->getTokenForRequest();
if (!empty($token)) {
$user = $this->retrieveByToken($token);
}
return $this->user = $user;
}
/**
* Get the token for the current request.
*
* @return string|null
*/
public function getTokenForRequest(): ?string {
$token = $this->request->query($this->inputKey);
if (empty($token)) {
$token = $this->request->input($this->inputKey);
}
if (empty($token)) {
$token = $this->request->bearerToken();
}
if (empty($token)) {
$token = $this->request->getPassword();
}
return $token;
}
/**
* Validate a user's credentials.
*
* @param array $credentials
* @return bool
*/
public function validate(array $credentials = []) {
if (empty($credentials[$this->inputKey])) {
return false;
}
if ($this->retrieveByToken($credentials[$this->inputKey])) {
return true;
}
return false;
}
/**
* Set the current request instance.
*
* @param Request $request
* @return $this
*/
public function setRequest(Request $request): self {
$this->request = $request;
return $this;
}
/**
* @param string $token
*
* @return Authenticatable|null
*/
public function retrieveByToken(string $token): ?Authenticatable {
$presenter = new TranslationPresenter();
$this->authenticator->check($presenter, $token);
return $presenter->getViewModel();
}
}
<?php
namespace App\Guards;
use Illuminate\Http\Request;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\Authenticatable;
use Source\UseCases\Doors\Authenticate\AuthenticateUseCase;
use Source\UseCases\Doors\Authenticate\TranslationPresenter;
class DoorGuard implements Guard {
use GuardHelpers;
/**
* The request instance.
*
* @var Request
*/
protected Request $request;
/**
* The name of the query string item from the request containing the API token.
*
* @var string
*/
protected string $inputKey;
/**
* @var AuthenticateUseCase
*/
protected AuthenticateUseCase $authenticator;
/**
* Create a new authentication guard.
*
* @param AuthenticateUseCase $authenticator
* @param Request $request
* @param string $inputKey
*/
public function __construct(
AuthenticateUseCase $authenticator,
Request $request,
$inputKey = 'api_token') {
$this->request = $request;
$this->inputKey = $inputKey;
$this->authenticator = $authenticator;
}
/**
* Get the currently authenticated user.
*
* @return Authenticatable|null
*/
public function user() {
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if ($this->user !== null) {
return $this->user;
}
$user = null;
$token = $this->getTokenForRequest();
if (!empty($token)) {
$user = $this->retrieveByToken($token);
}
return $this->user = $user;
}
/**
* Get the token for the current request.
*
* @return string|null
*/
public function getTokenForRequest(): ?string {
$token = $this->request->query($this->inputKey);
if (empty($token)) {
$token = $this->request->input($this->inputKey);
}
if (empty($token)) {
$token = $this->request->bearerToken();
}
if (empty($token)) {
$token = $this->request->getPassword();
}
return $token;
}
/**
* Validate a user's credentials.
*
* @param array $credentials
* @return bool
*/
public function validate(array $credentials = []) {
if (empty($credentials[$this->inputKey])) {
return false;
}
if ($this->retrieveByToken($credentials[$this->inputKey])) {
return true;
}
return false;
}
/**
* Set the current request instance.
*
* @param Request $request
* @return $this
*/
public function setRequest(Request $request): self {
$this->request = $request;
return $this;
}
/**
* @param string $token
*
* @return Authenticatable|null
*/
public function retrieveByToken(string $token): ?Authenticatable {
$presenter = new TranslationPresenter();
$this->authenticator->check($presenter, $token);
return $presenter->getViewModel();
}
}
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\ConfirmsPasswords;
class ConfirmPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Confirm Password Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password confirmations and
| uses a simple trait to include the behavior. You're free to explore
| this trait and override any functions that require customization.
|
*/
use ConfirmsPasswords;
/**
* Where to redirect users when the intended url fails.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
}
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
}
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
}
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{