Add authorization header

Add api key protection for all endpoints
Add registration list view
This commit is contained in:
Artur Savitskiy 2024-05-04 02:41:04 +02:00
parent c485b12c61
commit 5c2317d839
32 changed files with 369 additions and 54 deletions

2
backend/.htaccess Normal file
View File

@ -0,0 +1,2 @@
RewriteEngine On
RewriteRule .* - [e=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

View File

@ -8,6 +8,12 @@
require_once('../../utils/db.php');
require_once('../../utils/strings.php');
$authorization = $_SERVER["HTTP_AUTHORIZATION"];
if(strcmp($authorization, API_KEY) !== 0) {
echo 'STOP TRYING TO STEAL MY DATA!';
exit;
}
$method = $_SERVER['REQUEST_METHOD'];
if ('POST' === $method) {
parse_str(file_get_contents('php://input'), $_POST);

View File

@ -3,6 +3,12 @@
require_once('../../utils/db.php');
require_once('../../utils/strings.php');
$authorization = $_SERVER["HTTP_AUTHORIZATION"];
if(strcmp($authorization, API_KEY) !== 0) {
echo 'STOP TRYING TO STEAL MY DATA!';
exit;
}
$connection = connect();
$cid = intval($_GET["cid"]);

View File

@ -8,6 +8,12 @@
require_once('../../utils/db.php');
require_once('../../utils/tools.php');
$authorization = $_SERVER["HTTP_AUTHORIZATION"];
if(strcmp($authorization, API_KEY) !== 0) {
echo 'STOP TRYING TO STEAL MY DATA!';
exit;
}
$method = $_SERVER['REQUEST_METHOD'];
if ('POST' === $method) {
parse_str(file_get_contents('php://input'), $_POST);

View File

@ -0,0 +1,58 @@
<?php
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
require_once('../../utils/config.php');
require_once('../../utils/db.php');
require_once('../../utils/strings.php');
$authorization = $_SERVER["HTTP_AUTHORIZATION"];
if(strcmp($authorization, API_KEY) !== 0) {
echo 'STOP TRYING TO STEAL MY DATA!';
exit;
}
$connection = connect();
$returnValue = array();
$querystr = "SELECT * FROM li_registrations";
$result = mysqli_query($connection, $querystr);
if($result->num_rows !== 0) {
while ($row = mysqli_fetch_object($result)) {
$registration = (object) [
'rid' => $row->rid,
'firstname' => $row->firstname,
'lastname' => $row->lastname,
'birthday' => $row->birthday,
'gender' => $row->gender,
'street' => $row->street,
'house' => $row->house,
'zip' => $row->zip,
'city' => $row->city,
'phone' => $row->phone,
'email' => $row->email,
'accountholder' => $row->accountholder,
'iban' => $row->iban,
'bic' => $row->bic,
'bank' => $row->bank,
'applicationconsent' => $row->applicationconsent,
'datachangeconsent' => $row->datachangeconsent,
'privacypolicyconsent' => $row->privacypolicyconsent,
'directdebitconsent' => $row->directdebitconsent,
'returndebitconsent' => $row->returndebitconsent,
'datastorageconsent' => $row->datastorageconsent,
'multimediaconsent' => $row->multimediaconsent,
'registrationfrom' => $row->registrationfrom
];
array_push($returnValue, $registration);
}
}
mysqli_free_result($result);
echo json_encode($returnValue);
?>

View File

@ -8,6 +8,12 @@
require_once('../../utils/db.php');
require_once('../../utils/tools.php');
$authorization = $_SERVER["HTTP_AUTHORIZATION"];
if(strcmp($authorization, API_KEY) !== 0) {
echo 'STOP TRYING TO STEAL MY DATA!';
exit;
}
$method = $_SERVER['REQUEST_METHOD'];
if ('POST' === $method) {
parse_str(file_get_contents('php://input'), $_POST);

View File

@ -8,6 +8,12 @@
require_once('../../utils/db.php');
require_once('../../utils/strings.php');
$authorization = $_SERVER["HTTP_AUTHORIZATION"];
if(strcmp($authorization, API_KEY) !== 0) {
echo 'STOP TRYING TO STEAL MY DATA!';
exit;
}
$method = $_SERVER['REQUEST_METHOD'];
if ('POST' === $method) {
parse_str(file_get_contents('php://input'), $_POST);

View File

@ -3,12 +3,20 @@
require_once('../../utils/db.php');
require_once('../../utils/strings.php');
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Authorization");
$authorization = $_SERVER["HTTP_AUTHORIZATION"];
if(strcmp($authorization, API_KEY) !== 0) {
echo 'STOP TRYING TO STEAL MY DATA!';
exit;
}
$connection = connect();
$returnValue = array();
$querystr = "SELECT * FROM li_students";
$result = mysqli_query($connection, $querystr);
if($result->num_rows !== 0) {
while ($row = mysqli_fetch_object($result)) {
@ -56,6 +64,5 @@
mysqli_free_result($result);
header("Access-Control-Allow-Origin: *");
echo json_encode($returnValue);
?>

View File

@ -8,6 +8,12 @@
require_once('../../utils/db.php');
require_once('../../utils/tools.php');
$authorization = $_SERVER["HTTP_AUTHORIZATION"];
if(strcmp($authorization, API_KEY) !== 0) {
echo 'STOP TRYING TO STEAL MY DATA!';
exit;
}
$method = $_SERVER['REQUEST_METHOD'];
if ('POST' === $method) {
parse_str(file_get_contents('php://input'), $_POST);

View File

@ -8,6 +8,12 @@
require_once('../../utils/db.php');
require_once('../../utils/strings.php');
$authorization = $_SERVER["HTTP_AUTHORIZATION"];
if(strcmp($authorization, API_KEY) !== 0) {
echo 'STOP TRYING TO STEAL MY DATA!';
exit;
}
$method = $_SERVER['REQUEST_METHOD'];
if ('POST' === $method) {
parse_str(file_get_contents('php://input'), $_POST);

View File

@ -8,6 +8,12 @@
require_once('../../utils/db.php');
require_once('../../utils/strings.php');
$authorization = $_SERVER["HTTP_AUTHORIZATION"];
if(strcmp($authorization, API_KEY) !== 0) {
echo 'STOP TRYING TO STEAL MY DATA!';
exit;
}
$method = $_SERVER['REQUEST_METHOD'];
if ('POST' === $method) {
parse_str(file_get_contents('php://input'), $_POST);

View File

@ -8,6 +8,12 @@
require_once('../../utils/db.php');
require_once('../../utils/strings.php');
$authorization = $_SERVER["HTTP_AUTHORIZATION"];
if(strcmp($authorization, API_KEY) !== 0) {
echo 'STOP TRYING TO STEAL MY DATA!';
exit;
}
$method = $_SERVER['REQUEST_METHOD'];
if ('POST' === $method) {
parse_str(file_get_contents('php://input'), $_POST);

View File

@ -2,10 +2,11 @@
define('DB_SERVER','mysql');
define('DB_USER','db308647');
define('DB_PASSWORD','1&9r9t6u1r0');
define('DB_PASSWORD','9sql4L9!9A2r8');
define('DB_NAME','db308647');
define('IMAGE_PATH', dirname($_SERVER["REQUEST_URI"]) . '/images/');
define('MAGIC_WORD', 'AED717B292EE4F08A0AEE4EBA4B1B1FA');
define('MAGIC_DATE', 'YmdHi');
define('API_KEY', '754259b6-caf0-4eca-a1f6-812731adae79');
?>

View File

@ -41,8 +41,8 @@
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
"maximumWarning": "2mb",
"maximumError": "2mb"
},
{
"type": "anyComponentStyle",

View File

@ -10,7 +10,7 @@
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "ng lint",
"foramt": "npx prettier --write 'src/**/*.ts' 'src/**/*.html' 'src/**/*.scss'"
"format": "npx prettier --write 'src/**/*.ts' 'src/**/*.html' 'src/**/*.scss'"
},
"private": true,
"dependencies": {

View File

@ -3,13 +3,15 @@ import { RouterModule, Routes, provideRouter, withComponentInputBinding } from '
import { StudentListComponent } from './components/students/student-list/student-list.component';
import { VisitsComponent } from './components/visits/visits.component';
import { VisitsDatetimeComponent } from './components/visits/visits-datetime/visits-datetime.component';
import { StudentRegisterComponent } from './components/students/student-register/student-register.component';
import { RegistrationWizardComponent } from './components/registrations/registration-wizard/registration-wizard.component';
import { RegistrationListComponent } from './components/registrations/registration-list/registration-list.component';
const routes: Routes = [
{ path: 'students', component: StudentListComponent },
{ path: 'visits', component: VisitsComponent },
{ path: 'registrations', component: RegistrationListComponent },
{ path: 'select', component: VisitsDatetimeComponent },
{ path: 'register', component: StudentRegisterComponent },
{ path: 'register', component: RegistrationWizardComponent },
{ path: 'visits/:date/:time', component: VisitsComponent },
{ path: '**', redirectTo: 'students' },
];

View File

@ -20,7 +20,7 @@ import { VisitsDatetimeComponent } from './components/visits/visits-datetime/vis
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatNativeDateModule, MatRippleModule } from '@angular/material/core';
import {MY_DATE_FORMAT, StudentRegisterComponent} from './components/students/student-register/student-register.component';
import { MY_DATE_FORMAT, RegistrationWizardComponent} from './components/registrations/registration-wizard/registration-wizard.component';
import { MatStepperModule } from '@angular/material/stepper';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
@ -32,6 +32,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
import { MomentDateAdapter } from '@angular/material-moment-adapter';
import {AngularIbanModule} from "angular-iban";
import { RegistrationListComponent } from './components/registrations/registration-list/registration-list.component';
@NgModule({
declarations: [
@ -45,7 +46,8 @@ import {AngularIbanModule} from "angular-iban";
VisitsComponent,
StudentEnrollComponent,
VisitsDatetimeComponent,
StudentRegisterComponent,
RegistrationWizardComponent,
RegistrationListComponent,
],
imports: [
BrowserModule,

View File

@ -0,0 +1,63 @@
<div id="content">
<div class="grid-item-full">
<div *ngIf="loading; else table">
<mat-spinner class="center"></mat-spinner>
</div>
<ng-template #table>
<mat-table [dataSource]="dataSource">
<ng-container matColumnDef="Firstname">
<mat-header-cell *matHeaderCellDef> Vorname </mat-header-cell>
<mat-cell *matCellDef="let element">{{element.firstname}}</mat-cell>
</ng-container>
<ng-container matColumnDef="Lastname">
<mat-header-cell *matHeaderCellDef> Nachname </mat-header-cell>
<mat-cell *matCellDef="let element">{{element.lastname}}</mat-cell>
</ng-container>
<ng-container matColumnDef="Birthday">
<mat-header-cell *matHeaderCellDef> Geburtsdatum </mat-header-cell>
<mat-cell *matCellDef="let element">{{element.birthday | date: 'dd.MM.yyyy'}}</mat-cell>
</ng-container>
<ng-container matColumnDef="Gender">
<mat-header-cell *matHeaderCellDef> Geschlecht </mat-header-cell>
<mat-cell *matCellDef="let element">{{element.gender}}</mat-cell>
</ng-container>
<ng-container matColumnDef="Actions">
<mat-header-cell *matHeaderCellDef></mat-header-cell>
<mat-cell *matCellDef="let element" class="actions"><a><i class="bi bi-trash"></i></a></mat-cell>
</ng-container>
<mat-header-row
*matHeaderRowDef="[
'Firstname',
'Lastname',
'Birthday',
'Gender',
'Actions'
]"></mat-header-row>
<mat-row
*matRowDef="
let row;
columns: [
'Firstname',
'Lastname',
'Birthday',
'Gender',
'Actions'
]
"></mat-row>
</mat-table>
</ng-template>
</div>
<div class="grid-item">
<input
matInput
id="pipelineFilter"
(keyup)="applyFilter($event)"
placeholder="Filtern" />
</div>
<div class="grid-item"></div>
</div>

View File

@ -0,0 +1,74 @@
#content {
margin-top: 24px;
background-color: rgb(245, 126, 32);
color: #ffffff;
display: grid;
grid-template-columns: auto auto;
padding: 12px;
}
#pipelineFilter {
margin: 12px;
}
table {
width: 100%;
}
button {
color: white;
font-size: 1.5em;
}
button.button-add {
background-color: #411ccc;
}
.center {
width: 50%;
margin: auto;
}
a:link,
a:visited,
a:active {
text-decoration: none;
text-transform: none;
color: white;
}
a:hover {
color: #411ccc;
}
.grid-item-full {
grid-column-start: 1;
grid-column-end: 3;
}
.bi {
color: #411ccc;
font-size: 2em;
margin-right: 0.4em;
cursor: pointer;
}
.mat-mdc-row:hover .mat-mdc-cell {
background-color: #411ccc;
}
.mat-mdc-row:hover .mat-mdc-cell.actions .bi {
color: white;
}
#pipelineFilter {
display: block;
height: 2em;
margin-bottom: 1.5em;
font-size: 1em;
}
.actions a {
margin-left: 3em;
}

View File

@ -0,0 +1,53 @@
import { Component, OnInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { StudentRegistration } from 'src/app/models/student-registration';
import { RegistrationsService } from 'src/app/services/registrations/registrations.service';
@Component({
selector: 'li-registration-list',
templateUrl: './registration-list.component.html',
styleUrls: ['./registration-list.component.scss'],
})
export class RegistrationListComponent implements OnInit {
public loading: boolean = true;
public registrations: StudentRegistration[] = new Array<StudentRegistration>();
public dataSource = new MatTableDataSource<StudentRegistration>();
public constructor(
private registrationsService: RegistrationsService
) { }
public ngOnInit() {
this.dataSource.filterPredicate = function (record: any, filter: any) {
if (filter.length < 3) {
return true;
}
const searchIn =
record.firstname?.toLocaleLowerCase() +
record.lastname?.toLocaleLowerCase() +
record.street?.toLocaleLowerCase() +
record.email?.toLocaleLowerCase() || '';
const searchFor = filter.toLocaleLowerCase() || '';
return searchIn.indexOf(searchFor) >= 0;
};
this.getData();
}
public applyFilter(event: Event) {
const filterValue = (event.target as HTMLInputElement).value;
this.dataSource.filter = filterValue.trim().toLowerCase();
}
private getData() {
this.registrationsService.get().subscribe({
next: registrations => {
this.loading = false;
this.registrations = registrations;
this.dataSource.data = this.registrations;
},
});
}
}

View File

@ -17,11 +17,11 @@ export const MY_DATE_FORMAT= {
};
@Component({
selector: 'li-student-register',
templateUrl: './student-register.component.html',
styleUrls: ['./student-register.component.scss'],
selector: 'li-registration-wizard',
templateUrl: './registration-wizard.component.html',
styleUrls: ['./registration-wizard.component.scss'],
})
export class StudentRegisterComponent implements OnInit {
export class RegistrationWizardComponent implements OnInit {
firstFormGroup!: FormGroup;
secondFormGroup!: FormGroup;
thirdFormGroup!: FormGroup;

View File

@ -1,24 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { StudentRegisterComponent } from './student-register.component';
describe('StudentRegisterComponent', () => {
let component: StudentRegisterComponent;
let fixture: ComponentFixture<StudentRegisterComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [StudentRegisterComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(StudentRegisterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,4 +1,5 @@
export interface StudentRegistration {
rid?: number;
firstName: string;
lastName: string;
birthday: Date;

View File

@ -1,4 +1,4 @@
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Course } from 'src/app/models/course';
@ -9,12 +9,14 @@ import { environment } from 'src/environments/environment';
})
export class CoursesService {
private readonly serviceName = 'courses';
private readonly headers = { headers: new HttpHeaders({ "Authorization": environment.apiKey })};
constructor(private http: HttpClient) {}
public get(): Observable<Course[]> {
return this.http.get<Course[]>(
`${environment.apiUrl}${this.serviceName}/get.php`
`${environment.apiUrl}${this.serviceName}/get.php`,
this.headers
);
}
}

View File

@ -1,5 +1,5 @@
import { formatDate } from '@angular/common';
import { HttpClient, HttpParams } from '@angular/common/http';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Enrollment } from 'src/app/models/enrollment';
@ -11,6 +11,7 @@ import { environment } from 'src/environments/environment';
})
export class EnrollService {
private readonly serviceName = 'enroll';
private readonly headers = { headers: new HttpHeaders({ "Authorization": environment.apiKey })};
constructor(private http: HttpClient) {}
@ -20,7 +21,7 @@ export class EnrollService {
.set('date', formatDate(date, 'yyyy-MM-dd', ''));
return this.http.get<Enrollment>(
`${environment.apiUrl}${this.serviceName}/get.php`,
{ params: params }
{ params: params, headers: this.headers.headers }
);
}
@ -32,7 +33,8 @@ export class EnrollService {
return this.http.post<boolean>(
`${environment.apiUrl}${this.serviceName}/set.php`,
payload
payload,
this.headers
);
}
}

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { HttpClient } from "@angular/common/http";
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { StudentRegistration } from "../../models/student-registration";
import { Observable } from "rxjs";
import { environment } from 'src/environments/environment';
@ -9,8 +9,16 @@ import { environment } from 'src/environments/environment';
})
export class RegistrationsService {
private readonly serviceName = 'registrations';
private readonly headers = { headers: new HttpHeaders({ "Authorization": environment.apiKey })};
constructor(private http: HttpClient) { }
public get(): Observable<StudentRegistration[]> {
return this.http.get<StudentRegistration[]>(
`${environment.apiUrl}${this.serviceName}/get.php`, this.headers
);
}
public set(registration: StudentRegistration): Observable<void> {
const formatDate = (date: Date): string => date.toISOString().split('T')[0];
@ -38,7 +46,8 @@ export class RegistrationsService {
return this.http.post<void>(`${environment.apiUrl}${this.serviceName}/set.php`,
payload
payload,
this.headers
);
}
}

View File

@ -1,4 +1,4 @@
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Student } from 'src/app/models/student';
@ -9,12 +9,13 @@ import { environment } from 'src/environments/environment';
})
export class StudentsService {
private readonly serviceName = 'students';
private readonly headers = { headers: new HttpHeaders({ "Authorization": environment.apiKey })};
constructor(private http: HttpClient) {}
public get(): Observable<Student[]> {
return this.http.get<Student[]>(
`${environment.apiUrl}${this.serviceName}/get.php`
`${environment.apiUrl}${this.serviceName}/get.php`, this.headers
);
}
@ -26,7 +27,8 @@ export class StudentsService {
return this.http.post<void>(
`${environment.apiUrl}${this.serviceName}/set.php`,
payload
payload,
this.headers
);
}
@ -35,7 +37,8 @@ export class StudentsService {
return this.http.post<void>(
`${environment.apiUrl}${this.serviceName}/del.php`,
payload
payload,
this.headers
);
}
}

View File

@ -1,5 +1,5 @@
import { DatePipe, formatDate } from '@angular/common';
import { HttpClient, HttpParams } from '@angular/common/http';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { CourseVisit } from 'src/app/models/course-visit';
@ -11,6 +11,7 @@ import { environment } from 'src/environments/environment';
})
export class VisitsService {
private readonly serviceName = 'visits';
private readonly headers = { headers: new HttpHeaders({ "Authorization": environment.apiKey })};
constructor(private http: HttpClient) {}
@ -20,7 +21,8 @@ export class VisitsService {
// Not easy to pass "time" over GET
return this.http.post<CourseVisit>(
`${environment.apiUrl}${this.serviceName}/get.php`,
payload
payload,
this.headers
);
}
@ -29,7 +31,8 @@ export class VisitsService {
return this.http.post<boolean>(
`${environment.apiUrl}${this.serviceName}/set.php`,
payload
payload,
this.headers
);
}
@ -39,7 +42,8 @@ export class VisitsService {
return this.http.post<boolean>(
`${environment.apiUrl}${this.serviceName}/del.php`,
payload
payload,
this.headers
);
}
}

View File

@ -1,4 +1,5 @@
export const environment = {
production: true,
apiUrl: 'https://li-dance.de/plan/api/',
apiKey: '754259b6-caf0-4eca-a1f6-812731adae79',
};

View File

@ -5,6 +5,7 @@
export const environment = {
production: false,
apiUrl: 'https://li-dance.de/plan/api/',
apiKey: '754259b6-caf0-4eca-a1f6-812731adae79',
};
/*