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: networks:
doorcode: doorcode:
driver: bridge driver: bridge
secrets:
webserver_cert:
file: ./secrets/certs/webserver.cert
webserver_key:
file: ./secrets/certs/webserver.key
volumes:
db-data:
services: services:
webserver: webserver:
image: nginx:1-alpine image: nginx:1-alpine
container_name: webserver container_name: webserver
restart: unless-stopped restart: unless-stopped
tty: true tty: true
secrets:
- webserver_cert
- webserver_key
ports: ports:
- "8080:80" - "8080:443"
volumes: volumes:
- ./src/web:/var/www - ./src/web:/var/www
- ./nginx/conf.d/:/etc/nginx/conf.d/ - ./nginx/conf.d/:/etc/nginx/conf.d/
...@@ -27,12 +39,14 @@ services: ...@@ -27,12 +39,14 @@ services:
networks: networks:
- doorcode - doorcode
postgres: postgres:
image: postgres:12-alpine image: postgres:11-alpine
container_name: postgres container_name: postgres
restart: unless-stopped restart: unless-stopped
tty: true tty: true
ports: ports:
- "5432:5432" - "5432:5432"
volumes:
- db-data:/var/lib/postgresql/data
environment: environment:
POSTGRES_USER: web POSTGRES_USER: web
POSTGRES_PASSWORD: secret POSTGRES_PASSWORD: secret
...@@ -46,6 +60,8 @@ services: ...@@ -46,6 +60,8 @@ services:
tty: true tty: true
ports: ports:
- "8081:80" - "8081:80"
depends_on:
- postgres
environment: environment:
PHP_PG_ADMIN_SERVER_HOST: postgres PHP_PG_ADMIN_SERVER_HOST: postgres
PHP_PG_ADMIN_SERVER_DEFAULT_DB: doorcode 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 # 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... # on the same server. So we'll just do a proxy pass...
upstream localhost.api { upstream localhost.api {
server 127.0.0.1; server 127.0.0.1:443;
} }
server { server {
listen 80; listen 443 ssl;
index index.php index.html; index index.php index.html;
server_name localhost; server_name localhost;
ssl_certificate /run/secrets/webserver_cert;
ssl_certificate_key /run/secrets/webserver_key;
error_log /var/log/nginx/error.log; error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log; access_log /var/log/nginx/access.log;
root /var/www/frontend; root /var/www/frontend;
...@@ -38,7 +42,7 @@ server { ...@@ -38,7 +42,7 @@ server {
} }
location /api { location /api {
proxy_pass http://localhost.api/; proxy_pass https://localhost.api/;
} }
location / { location / {
...@@ -48,9 +52,13 @@ server { ...@@ -48,9 +52,13 @@ server {
server { server {
listen 80; listen 443 ssl;
index index.php index.html; index index.php index.html;
server_name localhost.api; server_name localhost.api;
ssl_certificate /run/secrets/webserver_cert;
ssl_certificate_key /run/secrets/webserver_key;
error_log /var/log/nginx/error.log; error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log; access_log /var/log/nginx/access.log;
root /var/www/backend/public; 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 = [ ...@@ -27,7 +27,7 @@ $config = [
* external url, no matter where you come from (direct access or via the * external url, no matter where you come from (direct access or via the
* reverse proxy). * reverse proxy).
*/ */
'baseurlpath' => 'http://localhost:8080/simplesaml/', 'baseurlpath' => 'https://localhost:8080/simplesaml/',
/* /*
* The 'application' configuration array groups a set configuration options * 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; ...@@ -6,7 +6,9 @@ use Exception;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Source\Exceptions\EntityExistsException; use Source\Exceptions\EntityExistsException;
use Illuminate\Auth\AuthenticationException;
use Source\Exceptions\EntityNotFoundException; use Source\Exceptions\EntityNotFoundException;
use Illuminate\Validation\ValidationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler { class Handler extends ExceptionHandler {
...@@ -54,4 +56,30 @@ class Handler extends ExceptionHandler { ...@@ -54,4 +56,30 @@ class Handler extends ExceptionHandler {
return parent::render($request, $exception); 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;
/**