Merge pull request #1 from byecorps/main
Update production branch so it can be used
|
@ -0,0 +1 @@
|
|||
github: byemc
|
|
@ -1,2 +1,3 @@
|
|||
config.php
|
||||
vendor/
|
||||
vendor/
|
||||
.idea/
|
|
@ -1,5 +0,0 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
|
@ -1,5 +0,0 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
|
@ -1,19 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="dataSourceStorageLocal" created-in="PS-232.10072.32">
|
||||
<data-source name="ByeCorps ID (local)" uuid="5bc27beb-c8ab-420d-bdbc-055b37ae9e39">
|
||||
<database-info product="MariaDB" version="10.6.12-MariaDB-0ubuntu0.22.04.1" jdbc-version="4.2" driver-name="MariaDB Connector/J" driver-version="3.0.7" dbms="MARIADB" exact-version="10.6.12" exact-driver-version="3.0">
|
||||
<extra-name-characters>#@</extra-name-characters>
|
||||
<identifier-quote-string>`</identifier-quote-string>
|
||||
</database-info>
|
||||
<case-sensitivity plain-identifiers="exact" quoted-identifiers="exact" />
|
||||
<secret-storage>master_key</secret-storage>
|
||||
<user-name>bye</user-name>
|
||||
<schema-mapping>
|
||||
<introspection-scope>
|
||||
<node kind="schema" qname="@" />
|
||||
</introspection-scope>
|
||||
</schema-mapping>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="ByeCorps ID (local)" uuid="5bc27beb-c8ab-420d-bdbc-055b37ae9e39">
|
||||
<driver-ref>mariadb</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mariadb://id:3306/id</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
|
@ -1,2 +0,0 @@
|
|||
#n:id
|
||||
!<md> [1700164719000, 0, null, null, -2147483648, -2147483648]
|
|
@ -1,2 +0,0 @@
|
|||
#n:information_schema
|
||||
!<md> [null, 0, null, null, -2147483648, -2147483648]
|
|
@ -1,2 +0,0 @@
|
|||
#n:mysql
|
||||
!<md> [null, 0, null, null, -2147483648, -2147483648]
|
|
@ -1,2 +0,0 @@
|
|||
#n:performance_schema
|
||||
!<md> [null, 0, null, null, -2147483648, -2147483648]
|
26
.idea/id.iml
|
@ -1,26 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/jean85/pretty-package-versions" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpmailer/phpmailer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-factory" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sentry/sentry" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/deprecation-contracts" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/options-resolver" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/erusev/parsedown" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/erusev/parsedown-extra" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/kornrunner/blurhash" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/id.iml" filepath="$PROJECT_DIR$/.idea/id.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -1,43 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCSFixerOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||
<option name="highlightLevel" value="WARNING" />
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpIncludePathManager">
|
||||
<include_path>
|
||||
<path value="$PROJECT_DIR$/vendor/psr/log" />
|
||||
<path value="$PROJECT_DIR$/vendor/jean85/pretty-package-versions" />
|
||||
<path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
|
||||
<path value="$PROJECT_DIR$/vendor/sentry/sentry" />
|
||||
<path value="$PROJECT_DIR$/vendor/guzzlehttp/psr7" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/options-resolver" />
|
||||
<path value="$PROJECT_DIR$/vendor/composer" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpmailer/phpmailer" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-factory" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-message" />
|
||||
<path value="$PROJECT_DIR$/vendor/erusev/parsedown" />
|
||||
<path value="$PROJECT_DIR$/vendor/erusev/parsedown-extra" />
|
||||
<path value="$PROJECT_DIR$/vendor/kornrunner/blurhash" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="8.1" />
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpUnit">
|
||||
<phpunit_settings>
|
||||
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" />
|
||||
</phpunit_settings>
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="PROJECT" dialect="MariaDB" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -77,8 +77,8 @@ if (isset($message )) {
|
|||
?>
|
||||
|
||||
<div id="wrapper">
|
||||
<div id="profile">
|
||||
<img src="<?= get_avatar_url($user['id']); ?>">
|
||||
<div id="mini_profile">
|
||||
<img src="<?= get_gravatar_url($user['email']); ?>">
|
||||
<div class="details">
|
||||
<span class="displayname"><?= $user['display_name'] ?></span>
|
||||
<span class="bcid"><?= format_bcid($user['id']); ?></span>
|
||||
|
|
|
@ -2,32 +2,22 @@
|
|||
// This file carries functions related to accounts.
|
||||
|
||||
function get_avatar_url($bcid):string {
|
||||
global $pdo;
|
||||
|
||||
$sql = "SELECT has_pfp FROM `accounts` WHERE id = ?";
|
||||
$exists = db_execute('SELECT public FROM avatars WHERE id = ? LIMIT 1', [$bcid]);
|
||||
|
||||
try {
|
||||
$stmt = $pdo -> prepare($sql);
|
||||
$stmt->execute([$bcid]);
|
||||
$has_pfp = $stmt->fetch();
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
die($e);
|
||||
}
|
||||
|
||||
$appendix = "default.png";
|
||||
|
||||
if ($has_pfp['has_pfp']) {
|
||||
$appendix = $bcid;
|
||||
}
|
||||
|
||||
return 'https://cdn.byecorps.com/id/profile/'.$appendix;
|
||||
if (empty($exists)) {
|
||||
return '/assets/default.png';
|
||||
}
|
||||
|
||||
return '/public/avatars/' . $bcid;
|
||||
}
|
||||
|
||||
function get_display_name($bcid, $use_bcid_fallback=true):string {
|
||||
function get_display_name($bcid, $use_bcid_fallback=true, $put_bcid_in_parenthesis=false, $format_bcid=false):string {
|
||||
$display_name = db_execute("SELECT display_name FROM accounts WHERE id = ?", [$bcid])['display_name'];
|
||||
if (!empty($display_name)) {
|
||||
if ($put_bcid_in_parenthesis) {
|
||||
return $display_name . " ($bcid)";
|
||||
}
|
||||
return $display_name;
|
||||
}
|
||||
|
||||
|
@ -38,6 +28,94 @@ function get_display_name($bcid, $use_bcid_fallback=true):string {
|
|||
return "";
|
||||
}
|
||||
|
||||
// Tokens so apps can get VERY BASIC information
|
||||
|
||||
function generate_basic_access_token($bcid, $application_id=""): array
|
||||
{
|
||||
// Returns an access token, a refresh token and an expiry timestamp.
|
||||
|
||||
$access_token = md5(uniqid(more_entropy: true).rand(1000000, 9999999));
|
||||
$refresh_token = md5(uniqid("rfish").rand(1000000, 9999999));
|
||||
|
||||
$valid_time = 12; // in hours
|
||||
$expiry = time() + ($valid_time * 60 * 60);
|
||||
|
||||
// echo $access_token . ":" . $refresh_token;
|
||||
|
||||
if ($application_id) {
|
||||
db_execute(
|
||||
"INSERT INTO tokens (access_token, refresh_token, expiry, owner_id, application_id, permissions) VALUES (?,?,?,?,?, (1<<0 | 1<<1))",
|
||||
[$access_token, $refresh_token, $expiry, $bcid, $application_id]
|
||||
);
|
||||
} else {
|
||||
db_execute(
|
||||
"INSERT INTO tokens (access_token, refresh_token, expiry, owner_id, permissions) VALUES (?,?,?,?, (1<<0 | 1<<1))",
|
||||
[$access_token, $refresh_token, $expiry, $bcid]
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
"access" => $access_token,
|
||||
"refresh" => $refresh_token,
|
||||
"expiry" => $expiry,
|
||||
"id" => $bcid
|
||||
];
|
||||
}
|
||||
|
||||
function generate_token($bcid, $application_id=null, $permissions=0): array {
|
||||
$access_token = md5(uniqid(more_entropy: true).rand(1000000, 9999999));
|
||||
$refresh_token = md5(uniqid("rfish").rand(1000000, 9999999));
|
||||
|
||||
$valid_time = 12; // in hours
|
||||
$expiry = time() + ($valid_time * 60 * 60);
|
||||
|
||||
db_execute(
|
||||
"INSERT INTO tokens (access_token, refresh_token, expiry, owner_id, application_id, permissions, type) VALUES (?,?,?,?,?,?, 'oauth')",
|
||||
[$access_token, $refresh_token, $expiry, $bcid, $application_id, $permissions]
|
||||
);
|
||||
|
||||
return [
|
||||
"access" => $access_token,
|
||||
"refresh" => $refresh_token,
|
||||
"permissions" => $permissions,
|
||||
"expiry" => $expiry,
|
||||
"id" => $bcid
|
||||
];
|
||||
}
|
||||
|
||||
function generate_cookie_access_token($bcid) {
|
||||
$access_token = md5(uniqid(prefix: "COOKIECOOKIECOOKIE", more_entropy: true).rand(1000000, 9999999));
|
||||
|
||||
$valid_time = 365 * 24; // 1 year
|
||||
$expiry = time() + ($valid_time * 60 * 60);
|
||||
|
||||
// echo $access_token . ":" . $refresh_token;
|
||||
|
||||
db_execute(
|
||||
"INSERT INTO tokens (access_token, expiry, owner_id, type) VALUES (?,?,?,'cookie')",
|
||||
[$access_token, $expiry, $bcid]
|
||||
);
|
||||
|
||||
return [
|
||||
"access" => $access_token,
|
||||
"expiry" => $expiry,
|
||||
"id" => $bcid
|
||||
];
|
||||
}
|
||||
|
||||
function validate_access_token($access_token): bool
|
||||
{
|
||||
$token_details = db_execute("SELECT * FROM tokens WHERE access_token = ?", [$access_token]);
|
||||
if (null == $token_details) {
|
||||
return false;
|
||||
}
|
||||
if (time() > $token_details['expiry']) {
|
||||
db_execute("DELETE FROM tokens where access_token = ?", [$access_token]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Password resets
|
||||
const PASSWORD_RESET_VALIDITY = 300; // in seconds.
|
||||
function create_password_reset($bcid):string {
|
||||
|
|
|
@ -18,6 +18,11 @@
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>API</h2>
|
||||
<ul>
|
||||
<li><a href="/admin/create/token">Token generator</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>Init</h2>
|
||||
<ul>
|
||||
<li>
|
||||
|
|
|
@ -21,9 +21,9 @@ $count = $count_req->fetchColumn();
|
|||
<ul>
|
||||
<?php
|
||||
foreach ($result as $row) {
|
||||
echo "<li><pre>";
|
||||
print_r($row);
|
||||
echo "</pre><p><a href='/admin/signinas?id=".$row['id']."'>Sign in as ".$row['display_name']."</a></li>";
|
||||
echo "<li>";
|
||||
echo $row['id'];
|
||||
echo "<p><a href='/admin/signinas?id=".$row['id']."'>Sign in as ".htmlspecialchars($row['display_name'])."</a></li>";
|
||||
}
|
||||
?>
|
||||
</ul>
|
||||
|
|
|
@ -13,7 +13,7 @@ function check_app_id($app_id): bool
|
|||
|
||||
if ($_SERVER['REQUEST_METHOD'] == "POST") {
|
||||
$app_id = generate_app_id();
|
||||
db_execute("INSERT INTO apps (id, owner_id, title, description) VALUES (?, ?, ?, ?)", [$app_id, $_POST['owner'], $_POST['title'], $_POST['description']]);
|
||||
db_execute("INSERT INTO apps (id, owner_id, title, description, type, callback) VALUES (?, ?, ?, ?, ?, ?)", [$app_id, $_POST['owner'], $_POST['title'], $_POST['description'], $_POST['type'], $_POST['callback']]);
|
||||
die();
|
||||
}
|
||||
|
||||
|
@ -23,11 +23,11 @@ if ($_SERVER['REQUEST_METHOD'] == "POST") {
|
|||
|
||||
<form method="post">
|
||||
<label for="title">Title</label>
|
||||
<input type="text" name="title" id="title">
|
||||
<input type="text" required name="title" id="title">
|
||||
<label for="description">Description</label>
|
||||
<textarea name="description" id="description" cols="30" rows="10"></textarea>
|
||||
<label for="owner">App owner</label>
|
||||
<select name="owner" id="owner">
|
||||
<select name="owner" required id="owner">
|
||||
<?php
|
||||
$users = db_query("SELECT * FROM accounts");
|
||||
foreach ($users as $row) {
|
||||
|
@ -35,5 +35,16 @@ if ($_SERVER['REQUEST_METHOD'] == "POST") {
|
|||
}
|
||||
?>
|
||||
</select>
|
||||
<button type="submit">Create app</button>
|
||||
<label for="type">App type</label>
|
||||
<select name="type" required id="type">
|
||||
<option value="null">None</option>
|
||||
<option value="basic_login">Basic login</option>
|
||||
</select>
|
||||
|
||||
<label for="app_icon">App icon</label>
|
||||
<input type="file" id="app_icon" name="app_icon" />
|
||||
|
||||
<label for="callback">Callback</label>
|
||||
<input type="url" id="callback" name="callback" />
|
||||
<button type="submit" class="primary">Create app</button>
|
||||
</form>
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
echo "<pre>";
|
||||
print_r($_POST);
|
||||
echo "</pre>";
|
||||
|
||||
$token = generate_token($_POST['owner'], $_POST['application'], $_POST['permissions']);
|
||||
|
||||
echo "<p>Created token. Access token: <code>". $token['access'] ."</code></p>";
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<h1>Token generator</h1>
|
||||
|
||||
<form method="post">
|
||||
<div class="container">
|
||||
<label for="owner">Token owner</label>
|
||||
<select name="owner" required id="owner">
|
||||
<?php
|
||||
$users = db_query("SELECT * FROM accounts");
|
||||
foreach ($users as $row) {
|
||||
echo "<option value='".$row['id']."'>".get_display_name($row['id'])." (".$row['id'].") </option>";
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="container">
|
||||
<label for="app">Token app</label>
|
||||
<select name="app" id="app">
|
||||
<option value="null">None</option>
|
||||
<?php
|
||||
$users = db_query("SELECT * FROM apps");
|
||||
foreach ($users as $row) {
|
||||
echo "<option value='".$row['id']."'>". $row['title'] ."</option>";
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="permissions" name="permissions" value="0" />
|
||||
|
||||
<h2>Permissions</h2>
|
||||
<p>Permission number: <span id="permissionnumber"></span></p>
|
||||
<div class="checkboxes container">
|
||||
<input type="checkbox" id="account.email" value="1" /><label for="account.email"><code>account.email</code></label>
|
||||
<input type="checkbox" id="account.settings" value="2" /><label for="account.settings"><code>account.settings</code></label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Generate!</button>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
form .container {
|
||||
display: unset;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const displayNumber = document.getElementById("permissionnumber");
|
||||
const permissionsInput = document.getElementById("permissions");
|
||||
const checkboxes = document.querySelectorAll("input[type='checkbox']");
|
||||
|
||||
console.log(checkboxes);
|
||||
|
||||
function updateCheckboxes() {
|
||||
let permissions = 0;
|
||||
|
||||
for (let checkbox of checkboxes) {
|
||||
if (checkbox.checked) {
|
||||
permissions += Number(checkbox.value);
|
||||
}
|
||||
}
|
||||
|
||||
displayNumber.innerText = permissions.toString();
|
||||
permissionsInput.value = permissions;
|
||||
}
|
||||
|
||||
for (let checkbox of checkboxes) {
|
||||
checkbox.onchange = updateCheckboxes;
|
||||
}
|
||||
|
||||
updateCheckboxes();
|
||||
|
||||
</script>
|
|
@ -6,13 +6,15 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|||
echo "<p>Create table `accounts`";
|
||||
$stmt = $pdo->prepare('create table accounts
|
||||
(
|
||||
id varchar(7) not null
|
||||
id varchar(7) not null
|
||||
primary key,
|
||||
email text not null,
|
||||
created_date date default current_timestamp() not null,
|
||||
display_name text null,
|
||||
password text not null,
|
||||
verified tinyint(1) not null,
|
||||
email text not null,
|
||||
created_date datetime default current_timestamp() not null,
|
||||
display_name text null,
|
||||
password text not null,
|
||||
verified tinyint(1) default 0 not null,
|
||||
has_pfp tinyint(1) default 0 not null,
|
||||
is_admin tinyint(1) default 0 not null,
|
||||
constraint email
|
||||
unique (email) using hash
|
||||
);');
|
||||
|
@ -23,7 +25,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|||
echo('<p>An error occurred: '. $e->getMessage() .'. Will skip. (Most likely the table already exists.)');
|
||||
}
|
||||
|
||||
echo '<p>Create the `password_resets` table</p>';
|
||||
echo '<p>Create the `password_resets` table';
|
||||
$stmt = $pdo->prepare('create table password_resets
|
||||
(
|
||||
id int auto_increment
|
||||
|
@ -33,11 +35,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|||
expiration int not null,
|
||||
constraint password_resets_ibfk_1
|
||||
foreign key (owner_id) references accounts (id)
|
||||
);
|
||||
|
||||
create index owner_id
|
||||
on password_resets (owner_id);
|
||||
');
|
||||
);');
|
||||
|
||||
try {
|
||||
$stmt->execute();
|
||||
|
@ -45,13 +43,106 @@ create index owner_id
|
|||
echo('<p>An error occurred: '. $e->getMessage() .'. Most likely this is already set.');
|
||||
}
|
||||
|
||||
echo '<p>Create the `apps` table';
|
||||
|
||||
try {
|
||||
db_execute('create table apps (
|
||||
id int auto_increment
|
||||
primary key,
|
||||
owner_id varchar(7) not null,
|
||||
title text not null,
|
||||
description text,
|
||||
image text default "https://id.byecorps.com/assets/default.png" not null,
|
||||
type text null,
|
||||
callback text null,
|
||||
constraint apps_ibfk_1
|
||||
foreign key (owner_id) references accounts (id)
|
||||
);');
|
||||
} catch (PDOException $e) {
|
||||
echo('<p>An error occurred: '. $e->getMessage() .'. Most likely this is already set.');
|
||||
}
|
||||
|
||||
|
||||
echo '<p>Create the `badges` table';
|
||||
|
||||
try {
|
||||
db_execute('create table badges (
|
||||
id int auto_increment
|
||||
primary key,
|
||||
app_id int not null,
|
||||
title text not null,
|
||||
description text,
|
||||
image text default "https://id.byecorps.com/assets/default.png" not null,
|
||||
type text null,
|
||||
callback text null,
|
||||
constraint badges_ibfk_1
|
||||
foreign key (app_id) references apps (id)
|
||||
);');
|
||||
} catch (PDOException $e) {
|
||||
echo('<p>An error occurred: '. $e->getMessage() .'. Most likely this is already set.');
|
||||
}
|
||||
|
||||
echo '<p>Create the `profiles` table';
|
||||
|
||||
try {
|
||||
db_execute('create table profiles (
|
||||
id varchar(7)
|
||||
primary key,
|
||||
description text null,
|
||||
public_avatar tinyint(1) default 0,
|
||||
public_display_name tinyint(1) default 0,
|
||||
|
||||
constraint profiles_ibfk_1
|
||||
foreign key (id) references accounts (id)
|
||||
);');
|
||||
} catch (PDOException $e) {
|
||||
echo('<p>An error occurred: '. $e->getMessage() .'. Most likely this is already set.');
|
||||
}
|
||||
|
||||
echo '<p>Create the `tokens` table';
|
||||
|
||||
try {
|
||||
db_execute('create table tokens (
|
||||
id int auto_increment primary key,
|
||||
access_token text unique,
|
||||
refresh_token text null,
|
||||
expiry int not null,
|
||||
owner_id varchar(7),
|
||||
application_id int(10) null,
|
||||
|
||||
constraint tokens_application_id
|
||||
foreign key (application_id) references apps (id),
|
||||
constraint tokens_owner_id
|
||||
foreign key (owner_id) references accounts (id)
|
||||
);');
|
||||
} catch (PDOException $e) {
|
||||
echo('<p>An error occurred: '. $e->getMessage() .'. Most likely this is already set.');
|
||||
}
|
||||
|
||||
echo '<p>Create the `tokens` table';
|
||||
|
||||
try {
|
||||
db_query('CREATE TABLE `badge_owners` (
|
||||
`badge_id` int(11) NOT NULL,
|
||||
`owner_id` varchar(7) NOT NULL,
|
||||
`earned` timestamp NULL DEFAULT current_timestamp(),
|
||||
`info` text DEFAULT NULL COMMENT \'App may attach more info about how the badge was won (Killed "CoolGamer69 in battle!")\',
|
||||
|
||||
constraint badges_owners_badge
|
||||
foreign key (badge_id) references badges (id),
|
||||
constraint badges_owners_owner
|
||||
foreign key (owner_id) references accounts (id)
|
||||
);');
|
||||
} catch (PDOException $e) {
|
||||
echo('<p>An error occurred: ' . $e->getMessage() . '. Most likely this is already set.');
|
||||
}
|
||||
|
||||
echo "<p>Database initialised.</p>";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<h2 class="subheading">Admin</h2>
|
||||
<h1>Init database</h1>
|
||||
|
||||
<p>Assuming you have the database config configured, you can click this button to create the tables required for this thing to function.</p>
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == "POST") {
|
||||
if ($_POST['purge'] == 'purge') {
|
||||
db_execute("DELETE FROM `password_resets` WHERE expiration < ?", [time()]);
|
||||
db_execute("DELETE FROM `tokens` WHERE expiry < ?", [time()]);
|
||||
}
|
||||
}
|
||||
|
||||
$expired_password_resets = db_execute("SELECT * FROM `password_resets` WHERE expiration < ?", [time()]);
|
||||
$expired_tokens = db_execute("SELECT * FROM `tokens` WHERE expiry < ?", [time()]);
|
||||
|
||||
?>
|
||||
|
||||
|
||||
|
||||
<h1>Purge</h1>
|
||||
<form method="post">
|
||||
<p>
|
||||
<button name="purge" value="purge" type="submit" class="primary">Purge</button>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<h2>Expired password resets</h2>
|
||||
<pre><?php print_r($expired_password_resets) ?></pre>
|
||||
|
||||
<h2>Expired Login tokens</h2>
|
||||
<pre><?php print_r($expired_tokens) ?></pre>
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
|
||||
$output_format = "json";
|
||||
header('Content-type: application/json');
|
||||
|
||||
if (array_key_exists('HTTP_AUTHORIZATION', $_SERVER)) {
|
||||
$access_token = str_replace("Bearer ", "", $_SERVER['HTTP_AUTHORIZATION']);
|
||||
}
|
||||
|
||||
if (!empty($access_token)) {
|
||||
// Check who the access token belongs to
|
||||
$token = db_execute("SELECT * FROM tokens WHERE access_token = ?", [$access_token]);
|
||||
// if the token doesn't exist...
|
||||
if (empty($token)) {
|
||||
|
||||
$invalid_token = true; // We won't tell this to the end-user immediately because I'd prefer to tell them about
|
||||
// 404 first.
|
||||
} else {
|
||||
$token_owner = $token['owner_id'];
|
||||
}
|
||||
}
|
||||
|
||||
function check_authorisation($token=""): int
|
||||
{
|
||||
global $token_owner;
|
||||
// Validate token
|
||||
if (!validate_access_token($token) && "" != $token) {
|
||||
return 0; // Unauthorised
|
||||
}
|
||||
|
||||
// Check the type of token
|
||||
$token_row = db_execute("SELECT * FROM tokens WHERE access_token = ?", [$token]);
|
||||
|
||||
if (null == $token_row) {
|
||||
if (array_key_exists('auth', $_SESSION)) {
|
||||
if ($_SESSION['auth']) {
|
||||
$token_row = [
|
||||
"type" => "dangerous"
|
||||
];
|
||||
$token_owner = $_SESSION['id'];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return match ($token_row['type']) {
|
||||
"dangerous" => 1<<0 | 1<<1, // Everything
|
||||
"basic" => 1<<1, // Basic
|
||||
"oauth" => $token_row['permissions'],
|
||||
default => 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Misc (unauthorised)
|
||||
|
||||
function redirect_to_documentation(): void
|
||||
{
|
||||
header('Location: /docs/api');
|
||||
}
|
||||
|
||||
// Health check
|
||||
|
||||
function api_health_check(): array
|
||||
{
|
||||
return ["message" => "Science compels us to explode the sun!", "time" => time(), "response_code" => 200];
|
||||
}
|
||||
|
||||
// Potentially authenticated image endpoints
|
||||
|
||||
function get_avatar(): array
|
||||
{
|
||||
if (!array_key_exists('id', $query)) {
|
||||
return [
|
||||
'response_code' => 404,
|
||||
'message' => 'ID not assigned/found'
|
||||
];
|
||||
}
|
||||
$user_id = $query['id'];
|
||||
return [];
|
||||
}
|
||||
|
||||
// User (REQUIRES AUTHORISATION)
|
||||
|
||||
function api_user_info(): array
|
||||
{
|
||||
global $access_token, $token_owner;
|
||||
// Authorisation levels:
|
||||
// `display_name` = 1 (basic)
|
||||
// `id` = 1 (basic)
|
||||
// `email` = 1 (basic)
|
||||
$level = check_authorisation($access_token);
|
||||
$data = null;
|
||||
if ($level & (1 << 0)) {
|
||||
$data = db_execute("SELECT id, email, display_name FROM accounts WHERE id = ? LIMIT 1", [$token_owner]);
|
||||
} else {
|
||||
$data = db_execute("SELECT id, display_name FROM accounts WHERE id = ? LIMIT 1", [$token_owner]);
|
||||
}
|
||||
|
||||
if (null != $data) {
|
||||
return [
|
||||
"response_code" => 200,
|
||||
"data" => $data
|
||||
];
|
||||
}
|
||||
|
||||
http_response_code(401);
|
||||
return [
|
||||
"response_code" => 401,
|
||||
"message" => "Unauthorized."
|
||||
];
|
||||
}
|
||||
|
||||
function api_settings(): array
|
||||
{
|
||||
// GET: Return all settings
|
||||
// POST/PATCH: Update settings
|
||||
|
||||
global $access_token, $token_owner;
|
||||
|
||||
$level = check_authorisation($access_token);
|
||||
|
||||
if (!($level & (1 << 1))) { // account.settings
|
||||
http_response_code(401);
|
||||
return [
|
||||
"response_code" => 401,
|
||||
"message" => "Unauthorized."
|
||||
];
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === "POST") {
|
||||
|
||||
// Now for the fucking worstest code ever
|
||||
$settings_changed = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (isset($settings_changed['account'])) {
|
||||
if (isset($settings_changed['account']['display_name'])) {
|
||||
$display_name = db_execute('UPDATE accounts SET display_name = ? WHERE id = ?',
|
||||
[$settings_changed['account']['display_name'], $token_owner]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get account settings
|
||||
$display_name = db_execute('SELECT display_name FROM accounts WHERE id = ?', [$token_owner])["display_name"];
|
||||
|
||||
|
||||
return [
|
||||
"response_code" => 200,
|
||||
"settings" => [
|
||||
"account" => [
|
||||
"display_name" => $display_name,
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$api_routes = [ // base url is base_url.'/api'
|
||||
// "/path" => "function_name"
|
||||
// Misc
|
||||
"" => "redirect_to_documentation",
|
||||
"/status" => "api_health_check",
|
||||
|
||||
// Account stuff
|
||||
"/account/me" => "api_user_info",
|
||||
|
||||
// Settings
|
||||
"/settings" => "api_settings",
|
||||
|
||||
// Get avatar
|
||||
"/avatars/get" => "get_avatar"
|
||||
];
|
||||
|
||||
$path = str_replace("/api", "", $path);
|
||||
|
||||
if (isset($api_routes[$path])) {
|
||||
if (isset($invalid_token)) {
|
||||
http_response_code(498);
|
||||
echo (json_encode([
|
||||
"response_code" => "498",
|
||||
"message" => "Token expired or invalid."
|
||||
]));
|
||||
exit();
|
||||
}
|
||||
$response = $api_routes[$path]();
|
||||
if (array_key_exists('response_code', $response)) {
|
||||
http_response_code($response['response_code']);
|
||||
}
|
||||
echo json_encode($response);
|
||||
} else {
|
||||
http_response_code(404);
|
||||
echo (json_encode([
|
||||
"response_code" => "404",
|
||||
"message" => "Route not found."
|
||||
]));
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -0.5 32 32" shape-rendering="crispEdges">
|
||||
<metadata>Made with Pixels to Svg https://codepen.io/shshaw/pen/XbxvNj</metadata>
|
||||
<path stroke="#cbdbfc" d="M2 4h28M2 5h1M29 5h1M0 6h3M29 6h3M0 7h1M31 7h1M0 8h1M31 8h1M0 9h1M31 9h1M0 10h1M31 10h1M0 11h1M31 11h1M0 12h1M31 12h1M0 13h1M31 13h1M0 14h1M31 14h1M0 15h1M31 15h1M0 16h1M31 16h1M0 17h1M31 17h1M0 18h1M31 18h1M0 19h1M31 19h1M0 20h1M31 20h1M0 21h1M31 21h1M0 22h1M31 22h1M0 23h1M31 23h1M0 24h1M31 24h1M0 25h3M29 25h3M2 26h1M29 26h1M2 27h28" />
|
||||
<path stroke="#ffffff" d="M3 5h26M3 6h26M1 7h30M1 8h30M1 9h30M1 10h6M8 10h1M10 10h6M21 10h1M25 10h6M1 11h7M9 11h9M19 11h3M23 11h2M26 11h5M1 12h6M12 12h6M19 12h3M23 12h2M26 12h5M1 13h6M12 13h6M19 13h3M23 13h2M26 13h5M1 14h6M12 14h6M19 14h3M23 14h2M26 14h5M1 15h6M12 15h6M19 15h3M23 15h2M26 15h5M1 16h15M21 16h1M25 16h6M1 17h6M12 17h19M1 18h5M13 18h18M1 19h5M13 19h3M24 19h1M26 19h5M1 20h5M13 20h18M1 21h5M13 21h3M17 21h1M21 21h1M26 21h5M1 22h30M1 23h30M1 24h30M3 25h26M3 26h26" />
|
||||
<path stroke="#000000" d="M7 10h1M9 10h1M16 10h5M22 10h3M8 11h1M18 11h1M22 11h1M25 11h1M7 12h5M18 12h1M22 12h1M25 12h1M7 13h5M18 13h1M22 13h1M25 13h1M7 14h5M18 14h1M22 14h1M25 14h1M7 15h5M18 15h1M22 15h1M25 15h1M16 16h5M22 16h3M7 17h5M6 18h7M6 19h7M16 19h8M25 19h1M6 20h7M6 21h7M16 21h1M18 21h3M22 21h4" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="300.858"
|
||||
height="50"
|
||||
viewBox="0 0 79.602012 13.229167"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
inkscape:export-filename="bitmap.png"
|
||||
inkscape:export-xdpi="192"
|
||||
inkscape:export-ydpi="192"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||
sodipodi:docname="bcidsignin.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="true"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showborder="false"
|
||||
labelstyle="default"
|
||||
inkscape:zoom="2.8649832"
|
||||
inkscape:cx="165.446"
|
||||
inkscape:cy="-36.300388"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1008"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer2" /><defs
|
||||
id="defs1" /><g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Layer 2"><rect
|
||||
style="display:inline;opacity:1;fill:#efdd8d;fill-opacity:1;stroke:#dcc455;stroke-width:0.261525;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect35"
|
||||
width="79.340492"
|
||||
height="12.967643"
|
||||
x="0.13076229"
|
||||
y="0.13076213"
|
||||
ry="2.0748229"
|
||||
rx="2.1097128"
|
||||
inkscape:label="Background" /><g
|
||||
style="shape-rendering:crispEdges"
|
||||
id="g32"
|
||||
transform="matrix(0.52916667,0,0,0.52916667,9.5250001,3.7041667)"
|
||||
inkscape:label="BCID person"><g
|
||||
id="layer1-5"
|
||||
inkscape:label="BCID logo"><g
|
||||
id="g27"
|
||||
inkscape:label="Body"
|
||||
style="opacity:1"><path
|
||||
id="rect26"
|
||||
style="opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 1.0058594,6.5 C 1.0026133,6.5 1,6.5026133 1,6.5058594 V 7.4941406 C 1,7.4973867 1.0026133,7.5 1.0058594,7.5 H 0.00585938 C 0.00261328,7.5 0,7.5026133 0,7.5058594 V 11.494141 C 0,11.497387 0.00261328,11.5 0.00585938,11.5 H 7.9941406 C 7.9973867,11.5 8,11.497387 8,11.494141 V 7.5058594 C 8,7.5026133 7.9973867,7.5 7.9941406,7.5 h -1 C 6.9973867,7.5 7,7.4973867 7,7.4941406 V 6.5058594 C 7,6.5026133 6.9973867,6.5 6.9941406,6.5 Z"
|
||||
inkscape:label="Body" /></g><g
|
||||
id="g31"
|
||||
inkscape:label="Head"><path
|
||||
id="rect28"
|
||||
style="opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 1.0058594,-0.5 C 1.0026133,-0.5 1,-0.49738672 1,-0.49414062 V 0.49414062 C 1,0.49738672 1.0026133,0.5 1.0058594,0.5 H 1.9941406 C 1.9973867,0.5 2,0.49738672 2,0.49414062 V -0.49414062 C 2,-0.49738672 1.9973867,-0.5 1.9941406,-0.5 Z m 2,0 C 3.0026133,-0.5 3,-0.49738672 3,-0.49414062 V 0.49414062 C 3,0.49738672 3.0026133,0.5 3.0058594,0.5 H 4.9941406 C 4.9973867,0.5 5,0.49738672 5,0.49414062 V -0.49414062 C 5,-0.49738672 4.9973867,-0.5 4.9941406,-0.5 Z m -1,1 C 2.0026133,0.5 2,0.50261328 2,0.50585938 V 1.4941406 C 2,1.4973867 2.0026133,1.5 2.0058594,1.5 h -1 C 1.0026133,1.5 1,1.5026133 1,1.5058594 V 5.4941406 C 1,5.4973867 1.0026133,5.5 1.0058594,5.5 H 6.9941406 C 6.9973867,5.5 7,5.4973867 7,5.4941406 V 1.5058594 C 7,1.5026133 6.9973867,1.5 6.9941406,1.5 h -4 C 2.9973867,1.5 3,1.4973867 3,1.4941406 V 0.50585938 C 3,0.50261328 2.9973867,0.5 2.9941406,0.5 Z"
|
||||
inkscape:label="Head" /></g></g></g><g
|
||||
id="text32"
|
||||
style="font-weight:500;font-size:4.58611px;font-family:Montserrat;-inkscape-font-specification:'Montserrat, Medium';letter-spacing:-0.079375px;word-spacing:0.0926042px;stroke-width:0;stroke-miterlimit:8.7"
|
||||
inkscape:label="Sign in"
|
||||
transform="scale(0.95030748,1.052291)"
|
||||
aria-label="Sign in with ByeCorps ID"><path
|
||||
style="font-weight:normal;-inkscape-font-specification:'Montserrat, Normal';opacity:1"
|
||||
d="m 19.328294,7.9399255 c 0.820914,0 1.196975,-0.4035777 1.196975,-0.875947 0,-1.1694581 -1.976613,-0.6374693 -1.976613,-1.5180025 0,-0.3210277 0.261408,-0.582436 0.84843,-0.582436 0.284339,0 0.605367,0.087136 0.880533,0.2659944 L 20.392272,4.9589539 C 20.13545,4.7800956 19.759389,4.6746151 19.397086,4.6746151 c -0.816327,0 -1.183216,0.4081638 -1.183216,0.8805331 0,1.1878025 1.976613,0.6466416 1.976613,1.5271747 0,0.3164416 -0.261408,0.5686777 -0.862189,0.5686777 -0.421922,0 -0.830085,-0.1651 -1.059391,-0.3898194 l -0.132997,0.2614083 c 0.238478,0.2476499 0.710847,0.417336 1.192388,0.417336 z m 1.952271,-2.9672133 c 0.137584,0 0.238478,-0.1054805 0.238478,-0.2384777 0,-0.123825 -0.105481,-0.2247194 -0.238478,-0.2247194 -0.132997,0 -0.238477,0.1054806 -0.238477,0.2293055 0,0.1284111 0.10548,0.2338916 0.238477,0.2338916 z m 0.160514,2.9396966 V 5.5001149 H 21.115465 V 7.9124088 Z M 24.19133,5.963312 C 23.989541,5.6468704 23.640997,5.4817705 23.237419,5.4817705 c -0.687917,0 -1.210733,0.4723693 -1.210733,1.1648719 0,0.6925027 0.522816,1.1740442 1.210733,1.1740442 0.394405,0 0.738364,-0.1605138 0.940153,-0.4677832 v 0.3072694 c 0,0.5961943 -0.279753,0.875947 -0.889706,0.875947 -0.371475,0 -0.720019,-0.123825 -0.949325,-0.3301999 l -0.1651,0.2476499 c 0.252236,0.2384777 0.683331,0.3714749 1.123597,0.3714749 0.797984,0 1.206147,-0.376061 1.206147,-1.2061469 V 5.5001149 H 24.19133 Z m -0.921808,1.5684497 c -0.531989,0 -0.912636,-0.3623027 -0.912636,-0.8851193 0,-0.5228165 0.380647,-0.8805331 0.912636,-0.8805331 0.531989,0 0.917222,0.3577166 0.917222,0.8805331 0,0.5228166 -0.385233,0.8851193 -0.917222,0.8851193 z m 3.296006,-2.0499912 c -0.417336,0 -0.74295,0.169686 -0.917222,0.4631971 V 5.5001149 H 25.33645 v 2.4122939 h 0.325614 V 6.6466424 c 0,-0.5503332 0.325614,-0.8713609 0.843844,-0.8713609 0.458611,0 0.724606,0.2614083 0.724606,0.7704665 v 1.3666608 h 0.325614 V 6.5136452 c 0,-0.6925026 -0.403578,-1.0318747 -0.9906,-1.0318747 z m 3.17623,-0.5090583 c 0.137583,0 0.238478,-0.1054805 0.238478,-0.2384777 0,-0.123825 -0.105481,-0.2247194 -0.238478,-0.2247194 -0.132997,0 -0.238478,0.1054806 -0.238478,0.2293055 0,0.1284111 0.105481,0.2338916 0.238478,0.2338916 z m 0.160514,2.9396966 V 5.5001149 H 29.576658 V 7.9124088 Z M 31.96002,5.4817705 c -0.417336,0 -0.74295,0.169686 -0.917222,0.4631971 V 5.5001149 h -0.311856 v 2.4122939 h 0.325614 V 6.6466424 c 0,-0.5503332 0.325614,-0.8713609 0.843844,-0.8713609 0.458611,0 0.724606,0.2614083 0.724606,0.7704665 V 7.9124088 H 32.95062 V 6.5136452 c 0,-0.6925026 -0.403578,-1.0318747 -0.9906,-1.0318747 z M 37.447649,7.5501061 36.677182,5.5001149 H 36.397429 L 35.622377,7.5501061 34.865669,5.5001149 h -0.311856 l 0.90805,2.4122939 h 0.307269 l 0.761295,-1.9766134 0.761294,1.9766134 h 0.307269 l 0.912636,-2.4122939 h -0.298097 z m 1.640415,-2.5773939 c 0.137583,0 0.238477,-0.1054805 0.238477,-0.2384777 0,-0.123825 -0.10548,-0.2247194 -0.238477,-0.2247194 -0.132998,0 -0.238478,0.1054806 -0.238478,0.2293055 0,0.1284111 0.10548,0.2338916 0.238478,0.2338916 z m 0.160514,2.9396966 V 5.5001149 h -0.325614 v 2.4122939 z m 1.984371,-0.3806471 c -0.09172,0.08255 -0.224719,0.123825 -0.357717,0.123825 -0.27058,0 -0.417336,-0.1559278 -0.417336,-0.4402666 V 5.7752815 h 0.733778 V 5.5001149 H 40.457896 V 4.9727122 h -0.325614 v 0.5274027 h -0.431094 v 0.2751666 h 0.431094 v 1.458383 c 0,0.4448527 0.252237,0.7016749 0.706261,0.7016749 0.188031,0 0.380648,-0.055033 0.509059,-0.1696861 z m 1.855963,-2.0499912 c -0.408164,0 -0.724606,0.1605138 -0.903464,0.4402665 V 4.5095151 h -0.325614 v 3.4028937 h 0.325614 V 6.6466424 c 0,-0.5503332 0.325614,-0.8713609 0.843844,-0.8713609 0.458611,0 0.724606,0.2614083 0.724606,0.7704665 v 1.3666608 h 0.325614 V 6.5136452 c 0,-0.6925026 -0.403578,-1.0318747 -0.9906,-1.0318747 z"
|
||||
id="path40" /><path
|
||||
style="font-weight:bold;-inkscape-font-specification:'Montserrat, Bold'"
|
||||
d="m 48.383925,6.2430647 c 0.261408,-0.1375833 0.426508,-0.3852332 0.426508,-0.7062609 0,-0.499886 -0.41275,-0.8346721 -1.215319,-0.8346721 h -1.56845 v 3.2102771 h 1.660172 c 0.843844,0 1.284111,-0.3210277 1.284111,-0.875947 0,-0.4035777 -0.229306,-0.6787443 -0.587022,-0.7933971 z M 47.503392,5.2616372 c 0.362302,0 0.559505,0.123825 0.559505,0.376061 0,0.2522361 -0.197203,0.3806472 -0.559505,0.3806472 H 46.765028 V 5.2616372 Z M 46.765028,7.3529034 V 6.5595063 h 0.866775 c 0.385233,0 0.591608,0.1284111 0.591608,0.3989916 0,0.2751666 -0.206375,0.3944055 -0.591608,0.3944055 z M 50.469192,7.1190118 49.776689,5.4450816 h -0.738364 l 1.068564,2.4856717 -0.0092,0.022931 c -0.09631,0.2201333 -0.206375,0.3072694 -0.403578,0.3072694 -0.142169,0 -0.293511,-0.059619 -0.403578,-0.1559277 l -0.261408,0.5090582 c 0.160514,0.1421694 0.43568,0.2247194 0.687917,0.2247194 0.444852,0 0.784224,-0.1788583 1.022702,-0.7750526 L 51.854197,5.4450816 H 51.16628 Z m 3.947232,-0.4310944 c 0,-0.793397 -0.559506,-1.2795247 -1.284111,-1.2795247 -0.752122,0 -1.316214,0.5319888 -1.316214,1.2703525 0,0.7337776 0.55492,1.2703525 1.407936,1.2703525 0.444853,0 0.788811,-0.1375833 1.018117,-0.3989916 L 53.861505,7.1373562 c -0.169686,0.1605139 -0.357717,0.2384777 -0.619125,0.2384777 -0.376061,0 -0.63747,-0.1880305 -0.706261,-0.4952998 h 1.866547 c 0.0046,-0.05962 0.01376,-0.1375834 0.01376,-0.1926167 z M 53.136899,5.9495537 c 0.321028,0 0.55492,0.2017888 0.605367,0.5136443 h -1.215319 c 0.05045,-0.3164416 0.284338,-0.5136443 0.609952,-0.5136443 z"
|
||||
id="path41" /><path
|
||||
style="font-weight:600;-inkscape-font-specification:'Montserrat, Semi-Bold'"
|
||||
d="m 56.400799,7.9582699 c 0.527402,0 0.976841,-0.1880305 1.274938,-0.5365749 L 57.290504,7.0548062 c -0.233892,0.2568222 -0.522816,0.3806472 -0.857602,0.3806472 -0.664986,0 -1.146528,-0.4677833 -1.146528,-1.1281831 0,-0.6603999 0.481542,-1.1281831 1.146528,-1.1281831 0.334786,0 0.62371,0.123825 0.857602,0.376061 L 57.675737,5.1928455 C 57.37764,4.8443012 56.928201,4.6562706 56.405385,4.6562706 c -0.986014,0 -1.719791,0.6925027 -1.719791,1.6509997 0,0.958497 0.733777,1.6509996 1.715205,1.6509996 z m 2.722731,-0.013758 c 0.761295,0 1.307042,-0.5228166 1.307042,-1.2565942 0,-0.7337776 -0.545747,-1.252008 -1.307042,-1.252008 -0.752122,0 -1.302455,0.5182304 -1.302455,1.252008 0,0.7337776 0.550333,1.2565942 1.302455,1.2565942 z m 0,-0.4907138 c -0.41275,0 -0.724605,-0.2980971 -0.724605,-0.7658804 0,-0.4677832 0.311855,-0.7658804 0.724605,-0.7658804 0.417336,0 0.729192,0.2980972 0.729192,0.7658804 0,0.4677833 -0.311856,0.7658804 -0.729192,0.7658804 z M 61.447281,5.463426 h -0.545747 v 2.4489828 h 0.573263 V 6.7246063 c 0,-0.499886 0.275167,-0.7567082 0.710848,-0.7567082 0.04127,0 0.08255,0.00459 0.132997,0.013758 V 5.4359094 c -0.408164,0 -0.706261,0.128411 -0.871361,0.3852332 z m 2.635605,-0.027517 c -0.334786,0 -0.619125,0.1146527 -0.811741,0.3485443 V 5.463426 h -0.545747 v 3.3386882 h 0.573263 V 7.6143117 c 0.197203,0.2247194 0.47237,0.3301999 0.784225,0.3301999 0.715433,0 1.242836,-0.4952999 1.242836,-1.2565942 0,-0.7567081 -0.527403,-1.252008 -1.242836,-1.252008 z m -0.06421,2.0178884 c -0.41275,0 -0.729192,-0.2980971 -0.729192,-0.7658804 0,-0.4677832 0.316442,-0.7658804 0.729192,-0.7658804 0.41275,0 0.724605,0.2980972 0.724605,0.7658804 0,0.4677833 -0.311855,0.7658804 -0.724605,0.7658804 z m 2.530125,0.4907138 c 0.687916,0 1.109838,-0.2980972 1.109838,-0.7567082 0,-0.958497 -1.513416,-0.5182304 -1.513416,-1.0135303 0,-0.1605139 0.1651,-0.2751666 0.513644,-0.2751666 0.233892,0 0.467784,0.045861 0.701675,0.1834444 l 0.220134,-0.4356805 c -0.220134,-0.1329972 -0.591609,-0.210961 -0.917222,-0.210961 -0.6604,0 -1.077736,0.3026832 -1.077736,0.7658804 0,0.9768414 1.513416,0.5365748 1.513416,1.0043581 0,0.169686 -0.151342,0.2705805 -0.513644,0.2705805 -0.30727,0 -0.63747,-0.1008945 -0.853017,-0.2430639 L 65.512345,7.669345 c 0.220133,0.1559277 0.628297,0.2751666 1.036461,0.2751666 z"
|
||||
id="path42" /><path
|
||||
style="font-weight:normal;-inkscape-font-specification:'Montserrat, Normal';opacity:1"
|
||||
d="M 69.775483,7.9124088 V 4.7021317 H 69.43611 v 3.2102771 z m 2.273299,0 c 1.027288,0 1.719791,-0.6603998 1.719791,-1.6051385 0,-0.9447387 -0.692503,-1.6051386 -1.719791,-1.6051386 H 70.74174 v 3.2102771 z m -0.96767,-2.916766 h 0.949325 c 0.853017,0 1.40335,0.541161 1.40335,1.3116275 0,0.7704665 -0.550333,1.3116275 -1.40335,1.3116275 h -0.949325 z"
|
||||
id="path43" /></g></g></svg>
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 698 B |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 721 B |
After Width: | Height: | Size: 2.3 KiB |
|
@ -1,9 +1,8 @@
|
|||
{
|
||||
"require": {
|
||||
"sentry/sdk": "^4.0",
|
||||
"phpmailer/phpmailer": "^6.8",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"erusev/parsedown-extra": "^0.8.1",
|
||||
"kornrunner/blurhash": "^1.2"
|
||||
"resend/resend-php": "^0.11.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "9baad85ecd1e18878c3fe588203305a1",
|
||||
"content-hash": "7cbb1a410be00f937b47c45077c89f9a",
|
||||
"packages": [
|
||||
{
|
||||
"name": "erusev/parsedown",
|
||||
|
@ -107,6 +107,215 @@
|
|||
},
|
||||
"time": "2019-12-30T23:20:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "7.8.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "41042bc7ab002487b876a0683fc8dce04ddce104"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104",
|
||||
"reference": "41042bc7ab002487b876a0683fc8dce04ddce104",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/promises": "^1.5.3 || ^2.0.1",
|
||||
"guzzlehttp/psr7": "^1.9.1 || ^2.5.1",
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"symfony/deprecation-contracts": "^2.2 || ^3.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-client-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"ext-curl": "*",
|
||||
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
|
||||
"php-http/message-factory": "^1.1",
|
||||
"phpunit/phpunit": "^8.5.36 || ^9.6.15",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Required for CURL handler support",
|
||||
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
|
||||
"psr/log": "Required for using the Log middleware"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Jeremy Lindblom",
|
||||
"email": "jeremeamia@gmail.com",
|
||||
"homepage": "https://github.com/jeremeamia"
|
||||
},
|
||||
{
|
||||
"name": "George Mponos",
|
||||
"email": "gmponos@gmail.com",
|
||||
"homepage": "https://github.com/gmponos"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://github.com/sagikazarmark"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle is a PHP HTTP client library",
|
||||
"keywords": [
|
||||
"client",
|
||||
"curl",
|
||||
"framework",
|
||||
"http",
|
||||
"http client",
|
||||
"psr-18",
|
||||
"psr-7",
|
||||
"rest",
|
||||
"web service"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/guzzle/issues",
|
||||
"source": "https://github.com/guzzle/guzzle/tree/7.8.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-12-03T20:35:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/promises",
|
||||
"version": "2.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/promises.git",
|
||||
"reference": "bbff78d96034045e58e13dedd6ad91b5d1253223"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223",
|
||||
"reference": "bbff78d96034045e58e13dedd6ad91b5d1253223",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"phpunit/phpunit": "^8.5.36 || ^9.6.15"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Promise\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle promises library",
|
||||
"keywords": [
|
||||
"promise"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/promises/issues",
|
||||
"source": "https://github.com/guzzle/promises/tree/2.0.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-12-03T20:19:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/psr7",
|
||||
"version": "2.6.1",
|
||||
|
@ -410,6 +619,58 @@
|
|||
],
|
||||
"time": "2023-08-29T08:26:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-client",
|
||||
"version": "1.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-client.git",
|
||||
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
|
||||
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.0 || ^8.0",
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Client\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP clients",
|
||||
"homepage": "https://github.com/php-fig/http-client",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-client",
|
||||
"psr",
|
||||
"psr-18"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-client"
|
||||
},
|
||||
"time": "2023-09-23T14:17:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-factory",
|
||||
"version": "1.0.2",
|
||||
|
@ -612,6 +873,63 @@
|
|||
},
|
||||
"time": "2019-03-08T08:55:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "resend/resend-php",
|
||||
"version": "v0.11.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/resend/resend-php.git",
|
||||
"reference": "31ec02fc2d16b3badc10612289a3325afe68147c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/resend/resend-php/zipball/31ec02fc2d16b3badc10612289a3325afe68147c",
|
||||
"reference": "31ec02fc2d16b3badc10612289a3325afe68147c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzlehttp/guzzle": "^7.5",
|
||||
"php": "^8.1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.13",
|
||||
"mockery/mockery": "^1.6",
|
||||
"pestphp/pest": "^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/Resend.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Resend\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Resend and contributors",
|
||||
"homepage": "https://github.com/resend/resend-php/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Resend PHP library.",
|
||||
"homepage": "https://resend.com/",
|
||||
"keywords": [
|
||||
"api",
|
||||
"client",
|
||||
"php",
|
||||
"resend",
|
||||
"sdk"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/resend/resend-php/issues",
|
||||
"source": "https://github.com/resend/resend-php/tree/v0.11.0"
|
||||
},
|
||||
"time": "2024-02-01T18:06:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sentry/sdk",
|
||||
"version": "4.0.0",
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
if (empty($_SESSION)) {
|
||||
http_response_code(307);
|
||||
header('Location: /signin?callback=/dashboard');
|
||||
exit();
|
||||
}
|
||||
if (!$_SESSION['auth']) {
|
||||
http_response_code(307);
|
||||
header('Location: /signin?callback=/dashboard');
|
||||
exit;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<link rel='stylesheet' href='/styles/settings.css' />
|
||||
|
||||
<div id="settings_split">
|
||||
<div id="mini_profile" class="left">
|
||||
<div class="image_container" data-backgroundcolour="white">
|
||||
<img src='<?= get_avatar_url($user['id']) ?>' />
|
||||
</div>
|
||||
<div class="texts">
|
||||
<span class="displayname"><?= htmlspecialchars(get_display_name($user['id'], false)) ?></span>
|
||||
<span class="id bcid"><?= format_bcid($user['id']) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class='right tiles'>
|
||||
<a href="/settings" class="tile">
|
||||
<div class="wrapper"> <!-- SUPERIMPORTANTBECAUSE IM BAD AT CSS -->
|
||||
<span class="fa-solid fa-cog icon"></span>
|
||||
<span class="text">Settings</span>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
<a href="/profile" class="tile">
|
||||
<div class="wrapper"> <!-- SUPERIMPORTANTBECAUSE IM BAD AT CSS -->
|
||||
<span class="fa-solid fa-id-card-clip icon"></span>
|
||||
<span class="text">Profile</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
|
@ -10,6 +10,15 @@ function db_execute($sql, $variables=[]) {
|
|||
|
||||
}
|
||||
|
||||
function db_execute_all($sql, $variables=[]) {
|
||||
global $pdo;
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($variables);
|
||||
return $stmt->fetchAll();
|
||||
|
||||
}
|
||||
|
||||
function db_query($sql) {
|
||||
global $pdo;
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# Errors
|
||||
|
||||
Here's all the error codes and what they mean.
|
||||
|
||||
| Error Code | Explanation |
|
||||
|-----------:|:----------------------------|
|
||||
| 12 | A generic error explaing something went wrong adding a password reset
|
|
@ -1,7 +1,4 @@
|
|||
<?php
|
||||
use PHPMailer\PHPMailer\SMTP;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
|
||||
if ($_SESSION['auth']) {
|
||||
header('Location: /account');
|
||||
|
@ -19,41 +16,24 @@ if ($_SERVER['REQUEST_METHOD'] == "POST") {
|
|||
// create a password reset
|
||||
$password_reset_link = create_password_reset($user['id']);
|
||||
|
||||
try {
|
||||
$safe_display_name = format_bcid($user['id']);
|
||||
} catch (Exception $e) {
|
||||
die("Bad BCID.");
|
||||
}
|
||||
$safe_display_name = get_display_name($user['id'], use_bcid_fallback: true);
|
||||
|
||||
if ($user['display_name'] != '') {
|
||||
$safe_display_name = $user['display_name'];
|
||||
}
|
||||
|
||||
$mail = new PHPMailer();
|
||||
|
||||
try {
|
||||
//Server settings
|
||||
// $mail->SMTPDebug = SMTP::DEBUG_SERVER; Verbose output
|
||||
$mail->isSMTP(); //Send using SMTP
|
||||
$mail->Host = MAIL_HOST; //Set the SMTP server to send through
|
||||
$mail->SMTPAuth = true; //Enable SMTP authentication
|
||||
$mail->Username = MAIL_USERNAME; //SMTP username
|
||||
$mail->Password = MAIL_PASSWORD; //SMTP password
|
||||
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; //Enable implicit TLS encryption
|
||||
$mail->Port = 465;
|
||||
|
||||
$mail->setFrom('id@byecorps.com', 'ByeCorps ID');
|
||||
$mail->addAddress($user['email'], $safe_display_name);
|
||||
$mail->addReplyTo('hello@byecorps.com', 'ByeCorps Support');
|
||||
|
||||
$mail->Subject = 'Reset your password';
|
||||
$mail->Body = 'Hey there '.$safe_display_name.'! Here is that password reset you requested. Just click the following link and you\'ll be sorted:
|
||||
$resend->emails->send([
|
||||
'from' => 'ByeCorps ID <noreply@id.byecorps.com>',
|
||||
'to' => [$safe_display_name . "<" . $user['email']. ">"],
|
||||
'subject' => 'Reset your password',
|
||||
'text' => 'Hey there '.$safe_display_name.'! Here is that password reset you requested. Just click the following link and you\'ll be sorted:
|
||||
'.$password_reset_link.'
|
||||
This link expires in 5 minutes.';
|
||||
|
||||
$mail->send();
|
||||
This link expires in 5 minutes.
|
||||
|
||||
If you did not request this password reset, please ignore it (or tighten your account\'s security)']);
|
||||
|
||||
// echo("<a href='$password_reset_link'>This is a security issue.</a>");
|
||||
} catch (Exception $e) {
|
||||
echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
|
||||
echo "Message could not be sent. Mailer Error: $e";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -65,7 +45,7 @@ This link expires in 5 minutes.';
|
|||
|
||||
<?php if(isset($message)) echo "<p>".$message."</p>"; ?>
|
||||
|
||||
<p>Forgot your password? We'll send you an email to reset it.</p>
|
||||
<p>Forgot your password? We'll email you to reset it.</p>
|
||||
|
||||
<form method="post">
|
||||
<input placeholder="a.dent@squornshellous.cloud" name="email" id="email" type="email">
|
||||
|
|
14
head.php
|
@ -7,7 +7,15 @@
|
|||
></script>
|
||||
<title><?php if (isset($doc_title)) { echo $doc_title." | "; } ?>ByeCorps ID</title>
|
||||
|
||||
<link rel="shortcut icon" href="favicon.svg" type="image/svg">
|
||||
<link rel="shortcut icon" href="/favicon.svg" type="image/svg" />
|
||||
|
||||
<link rel="stylesheet" href="/styles/global.css">
|
||||
<link rel="stylesheet" href="/fontawesome/css/all.css">
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
|
||||
<!-- 0_o -->
|
||||
|
||||
<link rel="apple-touch-icon" href="/assets/icons/apple-icon-180.png">
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
|
||||
<link rel="stylesheet" href="/styles/global.css" />
|
||||
<link rel="stylesheet" href="/fontawesome/css/all.css" />
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
if (array_key_exists(2, $uri)) {
|
||||
$avatar_links = db_execute('SELECT public FROM avatars WHERE id = ? LIMIT 1', [$uri[2]]);
|
||||
|
||||
if (empty($avatar_links)) {
|
||||
$fp = fopen('./assets/default.png', 'rb');
|
||||
} else {
|
||||
$fp = fopen(DATA_LOCATION . $avatar_links['public'], 'rb');
|
||||
}
|
||||
|
||||
header("Content-Type: image/png");
|
||||
header("Content-Length: " . filesize(DATA_LOCATION . $avatar_links['public']));
|
||||
|
||||
fpassthru($fp);
|
||||
exit;
|
||||
}
|
|
@ -3,37 +3,47 @@ require_once __DIR__ . '/vendor/autoload.php';
|
|||
|
||||
session_start();
|
||||
|
||||
use kornrunner\Blurhash\Blurhash;
|
||||
error_reporting(E_ERROR | E_WARNING | E_PARSE);
|
||||
|
||||
if (empty($_SESSION)) {
|
||||
$_SESSION['auth'] = false;
|
||||
}
|
||||
|
||||
include("config.php");
|
||||
include "config.php";
|
||||
|
||||
// MySQL
|
||||
$pdo = new PDO(DB_DSN, DB_USERNAME, DB_PASSWORD, PDO_OPTIONS);
|
||||
// Email
|
||||
if (defined("RESEND_API_KEY")) {
|
||||
$resend = Resend::client(RESEND_API_KEY);
|
||||
}
|
||||
|
||||
include("time_handler.php");
|
||||
require "misc_functions.php";
|
||||
require "database.php";
|
||||
include("time_handler.php");
|
||||
include("id_handler.php");
|
||||
include("accounts_handler.php");
|
||||
|
||||
if ($_SESSION['auth']) {
|
||||
$user = db_execute("SELECT * FROM `accounts` WHERE id = ? LIMIT 1", [$_SESSION['id']]);
|
||||
// Attempt to log the user in using their cookie if auth isn't set.
|
||||
if (!$_SESSION['auth']) {
|
||||
if (key_exists('keep_me_logged_in', $_COOKIE)) {
|
||||
if (validate_access_token($_COOKIE['keep_me_logged_in'])) {
|
||||
// Work out who the key belongs to
|
||||
$cookie_owner = db_execute("SELECT * FROM tokens WHERE access_token = ?", [$_COOKIE['keep_me_logged_in']]);
|
||||
if ($cookie_owner['type'] != "cookie") {
|
||||
setcookie('keep_me_logged_in', '', time()-3600);
|
||||
goto skip_cookie;
|
||||
}
|
||||
$_SESSION['auth'] = true;
|
||||
$_SESSION['id'] = $cookie_owner['owner_id'];
|
||||
|
||||
} else {
|
||||
setcookie('keep_me_logged_in', '', time()-3600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
\Sentry\init([
|
||||
'dsn' => SENTRY_DSN,
|
||||
// Specify a fixed sample rate
|
||||
'traces_sample_rate' => 1.0,
|
||||
// Set a sampling rate for profiling - this is relative to traces_sample_rate
|
||||
'profiles_sample_rate' => 1.0,
|
||||
]);
|
||||
|
||||
function does_variable_exists( $variable ) {
|
||||
return (isset($$variable)) ? "true" : "false";
|
||||
}
|
||||
skip_cookie:
|
||||
|
||||
$host_string = $_SERVER['HTTP_HOST'];
|
||||
$host = explode('.', $host_string);
|
||||
|
@ -45,6 +55,45 @@ if (str_ends_with($path,'/') && $path != "/") {
|
|||
exit;
|
||||
}
|
||||
$uri = array_values(array_filter(explode('/', $uri_string)));
|
||||
try {
|
||||
if ($_SESSION['auth']) {
|
||||
$user = db_execute("SELECT * FROM `accounts` WHERE id = ? LIMIT 1", [$_SESSION['id']]);
|
||||
if (!$user) {
|
||||
// Account doesn't exist. Log the user out.
|
||||
|
||||
// We won't redirect to the logout endpoint because if this is going off there's something
|
||||
// broken anyway.
|
||||
session_destroy();
|
||||
die("Your session was invalid so we've logged you out.");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception) {
|
||||
echo('<header>Database is broken. Please tell an admin.</header>');
|
||||
if ($uri_string == "/admin/init/database") { // Allows access to this page even if user doesn't have admin rights
|
||||
// because you can't check the rights.
|
||||
echo "<main>";
|
||||
include "admin_initdatabase.php";
|
||||
die ("</main>");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (defined("SENTRY_DSN")) {
|
||||
\Sentry\init([
|
||||
'dsn' => SENTRY_DSN,
|
||||
// Specify a fixed sample rate
|
||||
'traces_sample_rate' => 1.0,
|
||||
// Set a sampling rate for profiling - this is relative to traces_sample_rate
|
||||
'profiles_sample_rate' => 1.0,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
function does_variable_exists( $variable ) {
|
||||
return (isset($$variable)) ? "true" : "false";
|
||||
}
|
||||
|
||||
if(isset($query_string[1])) {
|
||||
$uri_string = $query_string[0];
|
||||
|
@ -65,29 +114,54 @@ $include = "404.html";
|
|||
|
||||
$paths = array(
|
||||
"/" => ["landing.php"],
|
||||
|
||||
"/admin" => ['admin.php'],
|
||||
"/admin/init/database" => ["admin_initdatabase.php"],
|
||||
"/admin/list/accounts" => ["admin_accounts.php"],
|
||||
"/admin/list/apps" => ["admin_apps.php"],
|
||||
"/admin/create/app" => ["admin_apps_create.php"],
|
||||
"/admin/create/token" => ["admin_create_token.php"],
|
||||
"/admin/signinas" => ["signinas.php"],
|
||||
"/admin/purge" => ["admin_purge.php"],
|
||||
|
||||
// Settings
|
||||
"/dashboard" => ["dashboard.php", "Dashboard"],
|
||||
"/settings" => ["settings.php", "Settings"],
|
||||
"/settings/account" => ["settings_account.php", "Settings -> Account"],
|
||||
|
||||
"/account" => ["account.php", "Your account"],
|
||||
"/signin" => ["signin.php", "Sign in"],
|
||||
"/signup" => ["signup.php", "Sign up"],
|
||||
"/signout" => ["signout.php", "Signed out"],
|
||||
"/forgot/password" => ["forgot_password.php", "Forgot password"],
|
||||
"/admin/signinas" => ["signinas.php"],
|
||||
"/reset/password" => ["reset_password.php", "Reset password"],
|
||||
"/docs" => ["docs.php", "Docs"],
|
||||
"/credits" => ["credits.html", "Credits"],
|
||||
"/credits" => ["credits.php", "Credits"],
|
||||
"/profile" => ["profile.php", "Profile"],
|
||||
|
||||
"/signin/external/basic" => ["login_external_basic.php"]
|
||||
);
|
||||
|
||||
if (!empty($uri) ) { // Go to jail. Go directly to jail. Do not pass Go.
|
||||
if ($uri[0] == "api") {
|
||||
include("api_handler.php");
|
||||
exit(); // fuck this shit i'm out
|
||||
}
|
||||
if ($uri[0] == "public" && $uri[1] == "avatars") {
|
||||
include("image_grabber.php");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
$migrated = false;
|
||||
if (isset($paths[$path])) {
|
||||
$include = $paths[$path][0];
|
||||
if (isset($paths[$path][1])) {
|
||||
$doc_title = $paths[$path][1];
|
||||
}
|
||||
if (isset($paths[$path][2])) {
|
||||
$migrated = $paths[$path][2];
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
|
@ -95,6 +169,12 @@ else {
|
|||
http_response_code(404);
|
||||
}
|
||||
|
||||
|
||||
if ($migrated) {
|
||||
$output = "";
|
||||
|
||||
include($include);
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
@ -105,8 +185,7 @@ else {
|
|||
<body>
|
||||
<?php include("header.php"); ?>
|
||||
<main>
|
||||
<?php
|
||||
|
||||
<?php
|
||||
if (!empty($uri)) {
|
||||
// print_r ($uri);
|
||||
|
||||
|
@ -116,16 +195,22 @@ else {
|
|||
|
||||
if ($uri[0] == "admin" && !$user['is_admin']) {
|
||||
http_response_code(401);
|
||||
die("<img src='https://http.cat/401.jpg'>");
|
||||
die("<img src='https://http.cat/401.jpg' alt='A cat standing in front of a door with a No Cats Allowed sign on it.' />");
|
||||
}
|
||||
|
||||
if ($uri[0] == "docs") {
|
||||
$include = "docs.php";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
include($include); ?>
|
||||
if ($migrated) {
|
||||
echo $output;
|
||||
}
|
||||
else {
|
||||
include ($include);
|
||||
}
|
||||
?>
|
||||
</main>
|
||||
<?php include("footer.php"); ?>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
34
landing.php
|
@ -1,14 +1,38 @@
|
|||
<?php
|
||||
|
||||
if (isset($_GET['new_landing'])) {
|
||||
goto new_landing;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="hero">
|
||||
<div class="hero-text">
|
||||
<img src="https://byecorps.b-cdn.net/id/bcid.svg" alt="ByeCorps ID Logo" class="logo">
|
||||
<img src="/assets/bcid.svg" alt="ByeCorps ID Logo" class="logo">
|
||||
<h1><span class="bc-1">Bye</span><span class="bc-2">Corps</span><span class="bc-3"> ID</span></h1>
|
||||
<p>Log into ByeCorps and beyond with a single ID.</p>
|
||||
<!-- <p><input type="email" name="loginEmail" id="loginEmail" placeholder="Email" /></p> -->
|
||||
|
||||
<?php
|
||||
if ( $_SESSION['auth']) { echo "<a href='/account' class='button primary'>Manage account</a>"; }
|
||||
else { echo "<a href='/signin' class='button primary'>Sign in</a><a href='/signup' class='button'>Create an account</a>"; }
|
||||
<?php
|
||||
if ( $_SESSION['auth']) { echo "<a href='/account' class='button primary'>Manage account</a>"; }
|
||||
else { echo "<a href='/signin' class='button primary'>Sign in</a><a href='/signup' class='button'>Create an account</a>"; }
|
||||
?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
|
||||
exit;
|
||||
|
||||
new_landing:
|
||||
|
||||
?>
|
||||
|
||||
<div class="hero">
|
||||
<div class="hero-text">
|
||||
<img src="/assets/bcid.svg" alt="ByeCorps ID Logo" class="logo">
|
||||
<h1><span class="bc-1">Bye</span><span class="bc-2">Corps</span><span class="bc-3"> ID</span></h1>
|
||||
<p>Log into ByeCorps and beyond with a single ID.</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
|
||||
<?php
|
||||
|
||||
// Disable warnings lol
|
||||
error_reporting(E_ALL ^ E_WARNING);
|
||||
|
||||
// Determine the app we are dealing with.
|
||||
|
||||
$flash = "";
|
||||
$error = "";
|
||||
|
||||
if (null != $query['appid']) {
|
||||
$app_id = $query['appid'];
|
||||
} else {
|
||||
$error = ["No app ID specified.", 200];
|
||||
goto login;
|
||||
}
|
||||
|
||||
$app = db_execute("SELECT * FROM apps WHERE id = ? LIMIT 1", [$app_id]);
|
||||
$doc_title = "Sign in to " . $app['title'];
|
||||
|
||||
// Lets check that the callback matches the app...
|
||||
if (null == $query['callback']) {
|
||||
$disable_logging_in = true;
|
||||
$error = ["No callback URL.", 400];
|
||||
goto login;
|
||||
}
|
||||
if ($query['callback'] != $app['callback']) {
|
||||
$disable_logging_in = true;
|
||||
$error = ["Callback URL doesn't match our records.", 400];
|
||||
goto login;
|
||||
}
|
||||
|
||||
if ($_SESSION['auth'] && $_SERVER['REQUEST_METHOD'] == 'GET') {
|
||||
// We can check if there's already a valid token of the same level and just pass that on instead.
|
||||
$valid_tokens = db_execute_all("SELECT * FROM tokens WHERE owner_id = ? AND type = ? AND application_id = ? AND expiry > ?",
|
||||
[$_SESSION['id'], "basic", $app_id, time()]);
|
||||
|
||||
if (sizeof($valid_tokens) > 0) {
|
||||
print_r($valid_tokens);
|
||||
|
||||
$token = $valid_tokens[0];
|
||||
|
||||
header('Location: '. $_GET['callback'].'?access_token='.$token['access_token'].'&refresh='.$token['refresh_token']
|
||||
.'&expiry='.$token['expiry']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// if (validate_access_token())
|
||||
|
||||
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Here's a few easy steps to figure out if we should give the other party a token or not.
|
||||
// print_r($_POST);
|
||||
|
||||
// First: match the session ids. If they aren't the same it's probably Not Ok.
|
||||
if (session_id() != $_POST['sessionid']) {
|
||||
echo "<h1>401 Unauthorised</h1><p>You are not permitted to view this content.</p>";
|
||||
exit(401);
|
||||
}
|
||||
|
||||
// Now let's determine if we're logged in or not. We can use the session for this, and verify using the
|
||||
// `bcid` value (which only appears if youre logged in!!!)
|
||||
if ($_SESSION['auth']) {
|
||||
if (null == $_POST['bcid'] || $_SESSION['id'] != $_POST['bcid']) {
|
||||
// Both of these suggest tampering,
|
||||
// let's log the user out and throw an error.
|
||||
$_SESSION['auth'] = false;
|
||||
$_SESSION['id'] = null;
|
||||
|
||||
$flash = "Sorry, something went wrong. Please sign in again.";
|
||||
|
||||
goto login;
|
||||
}
|
||||
}
|
||||
else { // of course, there's also the case that you WERENT logged in. Let's verify if you're logged in or not.
|
||||
$user_db_version = db_execute("SELECT * FROM accounts WHERE email = ?", [$_POST['email']]);
|
||||
if (!password_verify($_POST['password'], $user_db_version['password']) || null == $user_db_version) {
|
||||
// INCORRECT PASSWORD!!!!
|
||||
// or the account doesn't exist. we don't care either way.
|
||||
|
||||
$flash = "Incorrect email or password.";
|
||||
} else {
|
||||
// if it's correct, we'll still force them to click log in again anyway. I'll also be nice and set the
|
||||
// cookies properly.
|
||||
|
||||
$_SESSION['id'] = $user_db_version['id'];
|
||||
$_SESSION['auth'] = true;
|
||||
$user = $user_db_version;
|
||||
goto login;
|
||||
}
|
||||
}
|
||||
|
||||
// The following gets run assuming we know the client is the one CLICKING the button.
|
||||
$tokens = generate_basic_access_token($_POST['bcid'], $app_id);
|
||||
|
||||
header('Location: '. $_POST['callback'].'?access_token='.$tokens['access'].'&refresh='.$tokens['refresh']
|
||||
.'&expiry='.$tokens['expiry']);
|
||||
exit();
|
||||
|
||||
}
|
||||
|
||||
login:
|
||||
|
||||
?>
|
||||
|
||||
<div id="loginform">
|
||||
<?php if ("" != $error) {goto error_no_app;} ?>
|
||||
<div id="connection_img">
|
||||
<img src="<?= get_avatar_url($_SESSION['id']) ?>" alt="<?= htmlspecialchars($user['display_name']) ?>'s avatar" />
|
||||
<span class="sep">×</span>
|
||||
<img src="<?= $app['icon'] ?>" alt="<?= htmlspecialchars($user['title']) ?>'s avatar" />
|
||||
</div>
|
||||
<h1>Sign into <?= htmlspecialchars($app['title']) ?></h1>
|
||||
<p class="subtitle">Owned by <strong><?= htmlspecialchars( get_display_name($app['owner_id'], put_bcid_in_parenthesis: true) ) ?></strong></p>
|
||||
<!-- <p>--><?php //= htmlspecialchars($app['description']) ?><!--</p>-->
|
||||
<?php
|
||||
error_no_app:
|
||||
if ($error) {
|
||||
http_response_code($error[1]);
|
||||
echo "
|
||||
<div class='error center vertical-center'>
|
||||
<span class='fg-error fa-regular fa-2xl center fa-xmark-circle'></span>
|
||||
<h2>Something went wrong!</h2>
|
||||
<p>Server returned error:<br /><code>$error[0]</code> (HTTP response code $error[1])</p>
|
||||
</div>
|
||||
";
|
||||
goto dont_show_form;
|
||||
}
|
||||
?>
|
||||
<p><strong><?= htmlspecialchars($app['title']) ?></strong> uses ByeCorps ID for authentication.</p>
|
||||
<p>Please double-check the information and avoid signing in with your BCID if you do not trust this app.</p>
|
||||
<p>Please confirm that you'd like to sign into <strong><?= htmlspecialchars($app['title']) ?></strong>.</p>
|
||||
<?php
|
||||
if (null != $flash) {
|
||||
echo "<p class='flash'>$flash</p>";
|
||||
} else {
|
||||
echo "<br />";
|
||||
}
|
||||
?>
|
||||
<form class="login" method="post" action="">
|
||||
<input type="hidden" name="sessionid" value="<?= session_id() ?>" />
|
||||
<?php if ($_SESSION['auth'])
|
||||
{ $bcid = $user['id']; echo "<input type='hidden' name='bcid' value='$bcid' />";
|
||||
echo "<p class='subtitle'>You are signed in as ". get_display_name($_SESSION['id'],
|
||||
put_bcid_in_parenthesis: true) . ". <a>Not you?</a>.";
|
||||
goto signedin; } ?>
|
||||
<p class="subtitle">You will need to sign in first.</p>
|
||||
<input type="email" autocomplete="email" name="email" id="email" placeholder="Email" />
|
||||
<input type="password" name="password" id="password" placeholder="Password" />
|
||||
<?php signedin: ?>
|
||||
<button class="primary" type="submit">Sign into <?= htmlspecialchars($app['title']) ?></button>
|
||||
<p class="subtitle center">
|
||||
You will be brought to <strong><?= htmlspecialchars($query['callback']) ?></strong>.
|
||||
<br /><strong><?= htmlspecialchars($app['title']) ?></strong> will be able to see your email and display name.
|
||||
</p>
|
||||
<input type="hidden" name="callback" value="<?= $query['callback'] ?>" />
|
||||
</form>
|
||||
<?php dont_show_form: ?>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "ByeCorps ID",
|
||||
"short_name": "ByeCorps ID",
|
||||
"start_url": "/?pwa=true",
|
||||
|
||||
"display": "minimal-ui",
|
||||
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/icons/manifest-icon-192.maskable.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/icons/manifest-icon-192.maskable.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/assets/icons/manifest-icon-512.maskable.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/icons/manifest-icon-512.maskable.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
47
profile.php
|
@ -1,64 +1,23 @@
|
|||
|
||||
<link rel="stylesheet" href="/styles/profiles.css">
|
||||
<link rel="stylesheet" href="/styles/profiles.css" />
|
||||
|
||||
<?php
|
||||
|
||||
if (!$_SESSION['auth']) {
|
||||
header('Location: /signin?callback=/profile');
|
||||
exit();
|
||||
}
|
||||
|
||||
$profile = db_execute("SELECT * FROM `profiles` WHERE id = ? LIMIT 1", [$user['id']]);
|
||||
|
||||
if (empty($profile)) {
|
||||
$profile = [
|
||||
"id" => "0000000",
|
||||
"id" => "9999999",
|
||||
"public_display_name" => false,
|
||||
"public_avatar" => false,
|
||||
"description" => null,
|
||||
];
|
||||
}
|
||||
|
||||
$avatar = "https://cdn.byecorps.com/id/profile/default.png";
|
||||
$display_name = "";
|
||||
|
||||
if ($_SESSION['id'] != $profile['id']) {
|
||||
if ($profile['public_avatar']) {
|
||||
$avatar = get_avatar_url($profile['id']);
|
||||
}
|
||||
if ($profile['public_display_name']) {
|
||||
$display_name = get_display_name($profile['id'], false);
|
||||
}
|
||||
} else {
|
||||
$avatar = get_avatar_url($profile['id']);
|
||||
$display_name = get_display_name($profile['id'], false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
?>
|
||||
|
||||
<div id="profile">
|
||||
<img src="<?= $avatar ?>" class="avatar" alt="Avatar">
|
||||
<div class="info">
|
||||
<div class="displayname"><?= $display_name ?></div>
|
||||
<div class="bcid"><?= format_bcid( $profile['id'] ); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="details">
|
||||
<div id="badges">
|
||||
<h2>Badges</h2>
|
||||
</div>
|
||||
|
||||
<div id="info">
|
||||
<h2>Info</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Joined</th>
|
||||
<td><?= $user['created_date'] ?></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
if (empty($_SESSION)) {
|
||||
http_response_code(307);
|
||||
header('Location: /signin?callback=/dashboard');
|
||||
exit();
|
||||
}
|
||||
if (!$_SESSION['auth']) {
|
||||
http_response_code(307);
|
||||
header('Location: /signin?callback=/dashboard');
|
||||
exit;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<link href="/styles/settings.css" rel="stylesheet" />
|
||||
|
||||
<div id="settings_split">
|
||||
<div id="mini_profile" class="left">
|
||||
<div class="image_container" data-backgroundcolour="white">
|
||||
<img src=<?= get_avatar_url($user['id']) ?> />
|
||||
</div>
|
||||
<div class="texts">
|
||||
<span class="displayname"><?= htmlspecialchars(get_display_name($user['id'], false)) ?></span>
|
||||
<span class="id bcid"><?= format_bcid($user['id']) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul id="settings_list" class="right">
|
||||
<h1>Settings</h1>
|
||||
<li>
|
||||
<a href="/settings/account">
|
||||
<i class="fa-solid fa-fw fa-person icon"></i>
|
||||
Account
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/dashboard">
|
||||
<i class="fa-solid fa-fw fa-arrow-left icon"></i>
|
||||
Return to Dashboard
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
if (empty($_SESSION)) {
|
||||
http_response_code(307);
|
||||
header('Location: /signin?callback=/dashboard');
|
||||
exit();
|
||||
}
|
||||
if (!$_SESSION['auth']) {
|
||||
http_response_code(307);
|
||||
header('Location: /signin?callback=/dashboard');
|
||||
exit;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<link href="/styles/settings.css" rel="stylesheet" />
|
||||
|
||||
<div id="settings_split">
|
||||
<div id="mini_profile" class="left">
|
||||
<div class="image_container" data-backgroundcolour="white">
|
||||
<img src="<?= get_avatar_url($user['id']) ?>" alt="<?= htmlspecialchars(get_display_name($user['id'])) ?>'s avatar"/>
|
||||
</div>
|
||||
<div class="texts">
|
||||
<span class="displayname"><?= htmlspecialchars(get_display_name($user['id'], false)) ?></span>
|
||||
<span class="id bcid"><?= format_bcid($user['id']) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="other">
|
||||
<h1>Account</h1>
|
||||
<form method="post">
|
||||
<div class="container">
|
||||
<label for="display_name"><span id="display_name_label"></span> Display name</label>
|
||||
<input type="text" name="display_name" id="display_name" data-field="account.display_name" placeholder="<?= htmlspecialchars(format_bcid($user['id'])) ?>" value="<?= htmlspecialchars($user['display_name']) ?>">
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/*form label {*/
|
||||
/* padding-right: 1rem;*/
|
||||
/*}*/
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const display_name_box = document.getElementById("display_name");
|
||||
|
||||
async function updateJsonSettings(data) {
|
||||
const response = await fetch("https://id.byecorps.com/api/settings", {
|
||||
method: "POST", // or 'PUT'
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
console.log("Success:", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
display_name_box.onchange = _ => {
|
||||
document.getElementById("display_name_label").classList = "fa-solid fa-spinner fa-spin-pulse";
|
||||
|
||||
updateJsonSettings({
|
||||
account: {
|
||||
display_name: display_name_box.value,
|
||||
}
|
||||
}).then(_=>{
|
||||
document.getElementById("display_name_label").classList = "fa-solid fa-check";
|
||||
}).catch(_=>{
|
||||
document.getElementById("display_name_label").classList = "fa-solid fa-triangle-exclamation";
|
||||
});
|
||||
|
||||
}
|
||||
</script>
|
49
signin.php
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
if ($_SESSION['auth']) {
|
||||
header('Location: /account');
|
||||
header('Location: /profile');
|
||||
}
|
||||
|
||||
if (isset($query['callback'])) {
|
||||
|
@ -25,14 +25,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
if (password_verify($password, $user["password"])) {
|
||||
$_SESSION["id"] = $user["id"];
|
||||
$_SESSION["auth"] = true;
|
||||
//
|
||||
// print_r($_POST);
|
||||
// echo(is_string($_POST['keep_logged_in']));
|
||||
|
||||
if (array_key_exists('keep_logged_in', $_POST)) {
|
||||
if ($_POST['keep_logged_in'] == "on") {
|
||||
$token = generate_cookie_access_token($user['id']);
|
||||
// print_r($token);
|
||||
setcookie("keep_me_logged_in", $token['access'], time()+606024*365);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
if (isset($query['callback'])) {
|
||||
header("Location: ".$query['callback']);
|
||||
} else {
|
||||
header("Location: /profile");
|
||||
// echo "<pre>";
|
||||
// var_dump($user);
|
||||
// var_dump($_SESSION);
|
||||
// die();
|
||||
}
|
||||
|
||||
exit;
|
||||
|
@ -44,17 +53,21 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
?>
|
||||
|
||||
|
||||
<h2>Sign in to ByeCorps ID</h2>
|
||||
<?php
|
||||
if (isset($message)) {
|
||||
echo "<div class='flash'>$message</div>";
|
||||
}?>
|
||||
<form method="post">
|
||||
<input type="email" name="email" id="email" placeholder="Email">
|
||||
<input type="password" name="password" id="password" placeholder="Password">
|
||||
<button type="submit">Sign in</button>
|
||||
</form>
|
||||
<div id="loginform">
|
||||
<h2>Sign in to ByeCorps ID</h2>
|
||||
<?php
|
||||
if (isset($message)) {
|
||||
echo "<div class='flash'>$message</div>";
|
||||
}?>
|
||||
<form class="login" method="post">
|
||||
<input type="email" required name="email" id="email" placeholder="Email" />
|
||||
<input type="password" required name="password" id="password" placeholder="Password" />
|
||||
<div class="checkbox"><input type="checkbox" name="keep_logged_in" id="keep_logged_in" />
|
||||
<label for="keep_logged_in">Keep me logged in (for 365 days)</label></div>
|
||||
<button class="primary" type="submit">Sign in</button>
|
||||
</form>
|
||||
|
||||
<p class="center">
|
||||
<a href="/forgot/password">Forgot password?</a> • New? <a href="/register">Register</a> for a ByeCorps ID.
|
||||
</p>
|
||||
<p class="center">
|
||||
<a href="/forgot/password">Forgot password?</a> • New? <a href="/signup">Sign up</a> for a ByeCorps ID.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
$_SESSION['id'] = null;
|
||||
$_SESSION['auth'] = false;
|
||||
setcookie('keep_me_logged_in', '', time()-3600);
|
||||
session_destroy();
|
||||
|
||||
?>
|
||||
|
|
|
@ -55,7 +55,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
|
||||
<h2>Sign up for ByeCorps ID</h2>
|
||||
<form method="post">
|
||||
<input type="email" name="email" id="email" placeholder="Email">
|
||||
<input type="password" name="password" id="password" placeholder="Password">
|
||||
<input type="email" required name="email" id="email" placeholder="Email">
|
||||
<input type="password" required name="password" id="password" placeholder="Password">
|
||||
<button type="submit">Sign up</button>
|
||||
</form>
|
|
@ -24,9 +24,16 @@
|
|||
--grey-8: #343a40;
|
||||
--grey-9: #212529;
|
||||
|
||||
--error-color: var(--red-5);
|
||||
|
||||
--background: white;
|
||||
--background-dark: #121212;
|
||||
|
||||
--foreground: black;
|
||||
--foreground-dark: white;
|
||||
|
||||
--chip-background: var(--grey-0);
|
||||
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
|
@ -101,9 +108,27 @@ input[data-com-onepassword-filled="dark"] {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.flash {
|
||||
background: var(--red-5);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.fg-error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
@media screen and (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #121212;
|
||||
--foreground: white;
|
||||
|
||||
--chip-background: var(--grey-9);
|
||||
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
html {
|
||||
background: var(--background-dark, #121212);
|
||||
background: var(--background, #121212);
|
||||
}
|
||||
|
||||
button.primary, .button.primary {
|
||||
|
@ -121,7 +146,6 @@ input[data-com-onepassword-filled="dark"] {
|
|||
}
|
||||
|
||||
input, textarea {
|
||||
|
||||
background-color: #2c2c2c77;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,9 +15,9 @@ button, .button {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
header a {
|
||||
text-decoration: none;
|
||||
}
|
||||
/*header a {*/
|
||||
/* text-decoration: underline;*/
|
||||
/*}*/
|
||||
|
||||
/* inputs */
|
||||
|
||||
|
@ -46,6 +46,11 @@ input:disabled {
|
|||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.flash {
|
||||
padding: 1rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
table {
|
||||
background-color: var(--grey-2);
|
||||
width: 100%;
|
||||
|
@ -67,6 +72,23 @@ table > tbody > tr > td {
|
|||
padding: .5em;
|
||||
}
|
||||
|
||||
#connection_img {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#connection_img img {
|
||||
height:7.5rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#connection_img .sep {
|
||||
font-size: 4rem;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@media screen and (prefers-color-scheme: dark) {
|
||||
table {
|
||||
background-color: var(--grey-9);
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
}
|
||||
|
||||
body::after {
|
||||
content: "Development: Subject To Change";
|
||||
content: "BETA";
|
||||
position: fixed;
|
||||
top: 4.5rem;
|
||||
right: -1rem;
|
||||
top: 5rem;
|
||||
right: 1rem;
|
||||
|
||||
text-align: right;
|
||||
font-size: 2.5rem;
|
||||
|
|
|
@ -148,3 +148,8 @@ form {
|
|||
display: flex;
|
||||
}
|
||||
|
||||
#loginform {
|
||||
max-width: 500px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
|
||||
#profile {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#profile .avatar {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
#mini_profile {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
width: 750px;
|
||||
|
@ -12,18 +20,18 @@
|
|||
background: var(--grey-0);
|
||||
}
|
||||
|
||||
#profile > .avatar {
|
||||
#mini_profile > .avatar {
|
||||
height: 150px;
|
||||
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
#profile > .info > .displayname {
|
||||
#mini_profile > .info > .displayname {
|
||||
font-size: 2.5rem;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
#profile > .info > .bcid {
|
||||
#mini_profile > .info > .bcid {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
|
@ -32,8 +40,38 @@
|
|||
grid-template-columns: 3fr 1fr;
|
||||
}
|
||||
|
||||
#badges {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, 1fr);
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
#badges .badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.badge .details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.badge img {
|
||||
height: 7.5em;
|
||||
width: 7.5em;
|
||||
object-fit: contain;
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
.badge .details .title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@media screen and (prefers-color-scheme: dark) {
|
||||
#profile {
|
||||
#mini_profile {
|
||||
background: var(--grey-9);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
|
||||
main {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#settings_split {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
#mini_profile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
border-radius: 1.5rem;
|
||||
overflow: clip;
|
||||
|
||||
padding-bottom: 1.5rem;
|
||||
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
#mini_profile .image_container {
|
||||
display: flex;
|
||||
background: linear-gradient(to bottom, white, var(--background) 95%);
|
||||
}
|
||||
|
||||
#mini_profile .image_container img {
|
||||
width: 50%;
|
||||
margin: 0.5rem auto 0.5rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
#mini_profile .texts {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#mini_profile .texts .displayname {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#settings_list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#settings_list > h1 {
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
#settings_list li {
|
||||
border-top: var(--foreground) 1px solid;
|
||||
}
|
||||
|
||||
#settings_list li:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
#settings_list li > a {
|
||||
display: block;
|
||||
padding: 1rem 0;
|
||||
|
||||
color: var(--foreground);
|
||||
text-decoration: none;
|
||||
|
||||
transition: color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
#settings_list li > a:hover {
|
||||
color: var(--flax)
|
||||
}
|
||||
|
||||
.tiles {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: 150px;
|
||||
grid-auto-flow: dense;
|
||||
}
|
||||
|
||||
.tiles .tile {
|
||||
background: #343a40;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
|
||||
border-radius: 1.5rem;
|
||||
|
||||
transition: scale 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.tiles .tile:hover {
|
||||
scale: 1.05;
|
||||
}
|
||||
|
||||
.tiles .tile div {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.tiles .tile span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tile.double-height {
|
||||
grid-row: span 2;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 960px) {
|
||||
#settings_split {
|
||||
grid-template-columns: none;
|
||||
}
|
||||
|
||||
#mini_profile {
|
||||
flex-direction: row;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#mini_profile .image_container {
|
||||
width: 40%;
|
||||
background: linear-gradient(to right, white, var(--chip-background) 95%);
|
||||
}
|
||||
|
||||
#mini_profile .image_container img {
|
||||
margin: 1rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
#mini_profile .texts {
|
||||
flex: 1;
|
||||
margin: auto auto auto 0;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/* This file deals with font types and font families. */
|
||||
|
||||
@import url(https://fonts.bunny.net/css?family=montserrat:400,400i,600,600i,700,700i,900,900i);
|
||||
@import url(https://fonts.bunny.net/css2?family=courier+prime:wght@400;700&display=swap); /* for BCIDs */
|
||||
@import url(https://fonts.bunny.net/css?family=montserrat:400,400i,500,600,600i,700,700i,900,900i);
|
||||
@import url(https://fonts.bunny.net/css2?family=Space+Mono:wght@400;700&display=swap); /* for BCIDs */
|
||||
|
||||
@import url(/fontawesome/css/all.css);
|
||||
|
||||
|
@ -39,13 +39,23 @@ h2.subheading + h1 {
|
|||
}
|
||||
|
||||
.bcid {
|
||||
font-family: 'Courier Prime', monospace;
|
||||
font-family: 'Space Mono', monospace;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 0.9rem;
|
||||
margin: 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.vertical-center {
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
.icon-true::before {
|
||||
content: "\f00c";
|
||||
}
|
||||
|
@ -53,3 +63,28 @@ h2.subheading + h1 {
|
|||
.icon-false::before {
|
||||
content: "\f00d";
|
||||
}
|
||||
|
||||
.space-mono-regular {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.space-mono-regular-italic {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.space-mono-bold {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.space-mono-bold-italic {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
|