This commit is contained in:
gtoriadotnet 2021-12-09 17:47:26 -05:00
parent 3320857c12
commit 2c25af9efc
16 changed files with 303 additions and 114 deletions

View File

@ -37,8 +37,6 @@ class BannerController extends Controller
}
return response($content)
->header('Access-Control-Allow-Origin', env('APP_URL'))
->header('Vary', 'origin')
->header('Content-Type', 'application/json');
}
}

View File

@ -20,8 +20,6 @@ class GamesController extends Controller
->first();
return response()->json(['available' => $status->operational])
->header('Access-Control-Allow-Origin', env('APP_URL'))
->header('Vary', 'origin')
->header('Content-Type', 'application/json');
}
}

View File

@ -63,5 +63,6 @@ class Kernel extends HttpKernel
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'cors' => \App\Http\Middleware\Cors::class,
];
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class Cors
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$trustedHosts = explode(',', env('TRUSTED_HOSTS'));
$origin = parse_url($request->headers->get('origin'), PHP_URL_HOST);
$passCheck = false;
foreach($trustedHosts as &$host)
{
if(str_ends_with($origin, $host))
$passCheck = true;
}
$nextClosure = $next($request);
if($passCheck)
{
$nextClosure
->header('Access-Control-Allow-Origin', 'http' . ($request->secure() ? 's' : null) . '://' . $origin)
->header('Vary', 'origin');
}
return $nextClosure;
}
}

View File

@ -38,10 +38,10 @@ class RouteServiceProvider extends ServiceProvider
$this->configureRateLimiting();
$this->routes(function () {
Route::domain('api.' . env('APP_URL'))
Route::domain('apis.' . env('APP_URL'))
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
->group(base_path('routes/apis.php'));
Route::domain('www.' . env('APP_URL'))
->middleware('web')

9
web/resources/js/helpers/Auth.js vendored Normal file
View File

@ -0,0 +1,9 @@
// © XlXi 2021
// Graphictoria 5
function CreateAccount(loginForm)
{
}
export { CreateAccount };

View File

@ -37,7 +37,7 @@ class App extends React.Component {
function updateBanners()
{
axios.get(protocol + 'api.' + url + '/banners/data').then((response) => {
axios.get(protocol + 'apis.' + url + '/banners/data').then((response) => {
var result = [];
response.data.map(function(banner){
result.push(<Banner type={banner.type} description={banner.text} dismissible={banner.dismissable} />);

30
web/resources/js/layouts/Card.js vendored Normal file
View File

@ -0,0 +1,30 @@
// © XlXi 2021
// Graphictoria 5
import React from 'react';
const Card = (props) => {
return (
<div className="container graphictoria-center-vh">
<div className="card graphictoria-small-card shadow-sm">
<div className="card-body text-center">
{ props.children }
</div>
</div>
</div>
);
};
const CardTitle = (props) => {
return (
<>
<h5 className="card-title fw-bold">{ props.children }</h5>
<hr className="mx-5"/>
</>
);
};
export {
Card,
CardTitle
};

View File

@ -1,12 +1,19 @@
// © XlXi 2021
// Graphictoria 5
import React from "react";
import { Link } from "react-router-dom";
import React from 'react';
import { Link } from 'react-router-dom';
import ReCAPTCHA from "react-google-recaptcha";
import ReCAPTCHA from 'react-google-recaptcha';
import SetTitle from "../Helpers/Title.js";
import SetTitle from '../Helpers/Title.js';
import { CreateAccount } from '../Helpers/Auth.js';
import { Card, CardTitle } from '../Layouts/Card.js';
import LoginForm from './Auth/Login.js';
import ForgotPasswordForm from './Auth/ForgotPassword.js';
import RegisterForm from './Auth/Register.js';
class Auth extends React.Component {
componentDidMount()
@ -28,90 +35,29 @@ class Auth extends React.Component {
{
case '/login':
pageLabel = (<><i className="fas fa-user-circle"></i> SIGN IN</>);
pageContent = (
<>
<div className="col-md-8 mb-2">
<input type="username" className="form-control mb-4" placeholder="Username"/>
<input type="password" className="form-control mb-3" placeholder="Password"/>
<div className="d-flex mb-3">
<ReCAPTCHA
sitekey="6LeyHsUbAAAAAJ9smf-als-hXqrg7a-lHZ950-fL"
className="mx-auto"
/>
</div>
<button className="btn btn-primary px-5">SIGN IN</button><br/>
<Link to="/passwordreset" className="text-decoration-none fw-normal center">Forgot your password?</Link>
</div>
<div className="col">
<h5>New to Graphictoria?</h5>
<p>Creating an account takes less than a minute, and you can join a community of 6k+ users for <b>completely free</b>.<br/><Link to="/register" className="text-decoration-none fw-normal">Sign Up</Link></p>
</div>
</>
);
pageContent = (<LoginForm />);
break;
case '/register':
pageLabel = (<><i className="fas fa-user-plus"></i> REGISTER</>);
pageContent = (
<>
<div className="px-5 mb-4">
<div className="alert alert-warning graphictoria-alert" style={{ "borderRadius": "10px" }}>
<p className="mb-0">Make sure your password is unique!</p>
</div>
</div>
<div className="px-sm-5">
<input type="username" className="form-control" placeholder="Username" id="uname"/>
<p id="unameLabel" className="text-muted form-text mb-1"><br/></p>
<input type="email" className="form-control" placeholder="Email" id="email"/>
<p id="emailLabel" className="text-muted form-text mb-1">Make sure your email is valid, you'll need to confirm it.</p>
<input type="password" className="form-control" placeholder="Password" id="passwd"/>
<p id="passwdLabel" className="text-muted form-text mb-1"><br/></p>
<input type="password" className="form-control" placeholder="Confirm password" id="passwdconf"/>
<p id="passwdconfLabel" className="text-muted form-text pb-0 mb-0"><br/></p>
<div className="d-flex mb-3">
<ReCAPTCHA
sitekey="6LeyHsUbAAAAAJ9smf-als-hXqrg7a-lHZ950-fL"
className="mx-auto"
/>
</div>
<button className="btn btn-success px-5">SIGN UP</button><br/>
<Link to="/login" className="text-decoration-none fw-normal center">Already have an account?</Link>
<p className="text-muted my-2">By creating an account, you agree to our <a href="/legal/terms-of-service" className="text-decoration-none fw-normal" target="_blank">Terms of Service</a> and our <a href="/legal/privacy-policy" className="text-decoration-none fw-normal" target="_blank">Privacy Policy</a>.</p>
</div>
</>
<RegisterForm />
);
break;
case '/passwordreset':
pageLabel = (<><i className="fas fa-question-circle"></i> RESET PASSWORD</>);
pageContent = (
<div className="col-md-11 mx-auto">
<input type="username" className="form-control mb-3" placeholder="Username"/>
<div className="d-flex mb-3">
<ReCAPTCHA
sitekey="6LeyHsUbAAAAAJ9smf-als-hXqrg7a-lHZ950-fL"
className="mx-auto"
/>
</div>
<button className="btn btn-primary px-5">RESET PASSWORD</button><br/>
<Link to="/login" className="text-decoration-none fw-normal center">Login?</Link>
</div>
);
pageContent = (<ForgotPasswordForm />);
break;
default:
break;
}
return (
<div className="container graphictoria-center-vh">
<div className="card graphictoria-small-card shadow-sm">
<div className="card-body text-center">
<h5 className="card-title fw-bold">{ pageLabel }</h5>
<hr className="mx-5"/>
<div className="p-2 row">
{ pageContent }
</div>
</div>
<Card>
<CardTitle>{ pageLabel }</CardTitle>
<div className="p-2 row">
{ pageContent }
</div>
</div>
</Card>
);
}
}

View File

@ -0,0 +1,25 @@
// © XlXi 2021
// Graphictoria 5
import React from 'react';
import { Link } from 'react-router-dom';
import ReCAPTCHA from 'react-google-recaptcha';
const ForgotPasswordForm = (props) => {
return (
<div className="col-md-11 mx-auto">
<input type="username" className="form-control mb-3" placeholder="Username"/>
<div className="d-flex mb-3">
<ReCAPTCHA
sitekey="6LeyHsUbAAAAAJ9smf-als-hXqrg7a-lHZ950-fL"
className="mx-auto"
/>
</div>
<button className="btn btn-primary px-5">RESET PASSWORD</button><br/>
<Link to="/login" className="text-decoration-none fw-normal center">Login?</Link>
</div>
);
};
export default ForgotPasswordForm;

32
web/resources/js/pages/Auth/Login.js vendored Normal file
View File

@ -0,0 +1,32 @@
// © XlXi 2021
// Graphictoria 5
import React from 'react';
import { Link } from 'react-router-dom';
import ReCAPTCHA from 'react-google-recaptcha';
const LoginForm = (props) => {
return (
<>
<div className="col-md-8 mb-2">
<input type="username" className="form-control mb-4" placeholder="Username"/>
<input type="password" className="form-control mb-3" placeholder="Password"/>
<div className="d-flex mb-3">
<ReCAPTCHA
sitekey="6LeyHsUbAAAAAJ9smf-als-hXqrg7a-lHZ950-fL"
className="mx-auto"
/>
</div>
<button className="btn btn-primary px-5">SIGN IN</button><br/>
<Link to="/passwordreset" className="text-decoration-none fw-normal center">Forgot your password?</Link>
</div>
<div className="col">
<h5>New to Graphictoria?</h5>
<p>Creating an account takes less than a minute, and you can join a community of 6k+ users for <b>completely free</b>.<br/><Link to="/register" className="text-decoration-none fw-normal">Sign Up</Link></p>
</div>
</>
);
};
export default LoginForm;

107
web/resources/js/pages/Auth/Register.js vendored Normal file
View File

@ -0,0 +1,107 @@
// © XlXi 2021
// Graphictoria 5
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import ReCAPTCHA from 'react-google-recaptcha';
import Loader from '../../Components/Loader.js';
const RegisterForm = (props) => {
const RegistrationAreas = [
{
text: 'Username',
type: 'username',
id: 'username'
},
{
text: 'Email',
type: 'email',
id: 'email'
},
{
text: 'Password',
type: 'password',
id: 'password'
},
{
text: 'Confirm password',
type: 'password',
id: 'confirmation'
}
];
const [waitingForSubmission, setWaitingForSubmission] = useState(false);
const [values, setValues] = useState({
username: '',
email: '',
password: '',
confirmation: ''
});
const [validity, setValidity] = useState({
username: false,
email: false,
password: false,
confirmation: false
});
const [validityMessages, setValidityMessages] = useState({
username: 'test',
email: '',
password: '',
confirmation: ''
});
const handleChange = (e) => {
const {id, value} = e.target;
setValues(prevState => ({
...prevState,
[id] : value
}));
}
function SubmitRegistration()
{
setWaitingForSubmission(true);
}
return (
waitingForSubmission
?
<Loader />
:
(
<>
<div className="px-5 mb-2">
<div className="alert alert-warning graphictoria-alert" style={{ "borderRadius": "10px" }}>
<p className="mb-0">Make sure your password is unique!</p>
</div>
</div>
<div className="px-sm-5">
{
RegistrationAreas.map(({ text, type, id }, index) =>
<input key={ index } onChange={ handleChange } type={ type } className={ `form-control mb-2${ validityMessages[id] !== '' ? (validity[id] === true ? ' is-valid' : ' is-invalid') : ''}` } placeholder={ text } id={ id } />
)
}
<div className="mt-3">
<div className="d-flex mb-2">
<ReCAPTCHA
sitekey="6LeyHsUbAAAAAJ9smf-als-hXqrg7a-lHZ950-fL"
className="mx-auto"
/>
</div>
<button className="btn btn-success px-5" onClick={ SubmitRegistration }>SIGN UP</button>
</div>
<Link to="/login" className="text-decoration-none fw-normal center">Already have an account?</Link>
<p className="text-muted my-2">By creating an account, you agree to our <a href="/legal/terms-of-service" className="text-decoration-none fw-normal" target="_blank">Terms of Service</a> and our <a href="/legal/privacy-policy" className="text-decoration-none fw-normal" target="_blank">Privacy Policy</a>.</p>
</div>
</>
)
);
};
export default RegisterForm;

View File

@ -6,37 +6,34 @@ import { Link, useHistory } from "react-router-dom";
import SetTitle from "../Helpers/Title.js";
import { Card, CardTitle } from '../Layouts/Card.js';
function GenericErrorModal(props)
{
const history = useHistory();
return (
<div className="container graphictoria-center-vh">
<div className="card graphictoria-small-card shadow-sm">
<div className="card-body text-center">
<h5 className="card-title fw-bold">{ props.title }</h5>
<hr className="mx-5"/>
<p className="card-text">{ props.children }</p>
{
props.stack !== undefined && process.env.NODE_ENV === 'development'
?
<div className="border border-primary bg-dark p-3 m-4">
{/* this code is a jumbled mess */}
<code>
STACK TRACE<br/>{("-").repeat(15)}<br/>{ props.stack }
</code>
</div>
:
null
}
<div className="mt-2">
<Link className="btn btn-primary px-4 me-2" to="/home">Home</Link>
{/* eslint-disable-next-line */}
<a className="btn btn-secondary px-4" onClick={ () => history.goBack() }>Back</a>
<Card>
<CardTitle>{ props.title }</CardTitle>
<p className="card-text">{ props.children }</p>
{
props.stack !== undefined && process.env.NODE_ENV === 'development'
?
<div className="border border-primary bg-dark p-3 m-4">
{/* this code is a jumbled mess */}
<code>
STACK TRACE<br/>{("-").repeat(15)}<br/>{ props.stack }
</code>
</div>
</div>
:
null
}
<div className="mt-2">
<Link className="btn btn-primary px-4 me-2" to="/home">Home</Link>
{/* eslint-disable-next-line */}
<a className="btn btn-secondary px-4" onClick={ () => history.goBack() }>Back</a>
</div>
</div>
</Card>
);
}

View File

@ -31,7 +31,7 @@ class Games extends React.Component {
SetTitle('Games');
axios.get(protocol + 'api.' + url + '/games/metadata').then((response) => {
axios.get(protocol + 'apis.' + url + '/games/metadata').then((response) => {
app.setState({loading: !(response.data.available == false), offline: !response.data.available});
});
}

View File

@ -16,16 +16,20 @@ use App\Http\Controllers\GamesController;
|
*/
Route::get('/', function () {
return 'API OK';
});
Route::middleware(['cors'])->group(function() {
Route::get('/banners/data', [BannerController::class, 'getBanners']);
Route::get('/', function () {
return 'API OK';
});
Route::get('/games/metadata', [GamesController::class, 'isAvailable']);
Route::get('/banners/data', [BannerController::class, 'getBanners']);
Route::get('/games/metadata', [GamesController::class, 'isAvailable']);
Route::fallback(function () {
return response('{"errors":[{"code":404,"message":"NotFound"}]}', 404)
->header('Cache-Control', 'private')
->header('Content-Type', 'application/json; charset=utf-8');
});
Route::fallback(function () {
return response('{"errors":[{"code":404,"message":"NotFound"}]}', 404)
->header('Cache-Control', 'private')
->header('Content-Type', 'application/json; charset=utf-8');
});