Merge pull request 'Alpha Release 0.3.1 - API Keys' (#29) from dev into main
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
Reviewed-on: #29 Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
This commit is contained in:
commit
db91661556
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -2,16 +2,29 @@
|
|||
|
||||
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
|
||||
|
||||
#### [v0.3.1](https://git.odit.services/lfk/document-server/compare/v0.3.0...v0.3.1)
|
||||
|
||||
- 🚀Bumped version to v0.3.1 [`dcde424`](https://git.odit.services/lfk/document-server/commit/dcde424b77dcc9753859f94f7bcbe24fe3523c27)
|
||||
- Implemented basic auth [`bdeadd2`](https://git.odit.services/lfk/document-server/commit/bdeadd274bc0f9c8cbab35a8a5605bef4c22ba6c)
|
||||
- 🧾New changelog file version [CI SKIP] [skip ci] [`b7c6c6e`](https://git.odit.services/lfk/document-server/commit/b7c6c6e15708e471f5c3d0ca4cf11b1c08c88c9c)
|
||||
- Merge pull request 'API Key based auth feature/26-api_auth' (#27) from feature/26-api_auth into dev [`2d031da`](https://git.odit.services/lfk/document-server/commit/2d031dae035866a4aa247398ea68ff338ab58cbd)
|
||||
- 🧾New changelog file version [CI SKIP] [skip ci] [`e306cdb`](https://git.odit.services/lfk/document-server/commit/e306cdb2c8e58fc1aef79b95cba5d4cc96ac7658)
|
||||
- Added auth to openapi spec [`729f2d7`](https://git.odit.services/lfk/document-server/commit/729f2d7240b54ffe2d4db36cce29de0afdfc9417)
|
||||
- Added api key to env doc [`4543092`](https://git.odit.services/lfk/document-server/commit/454309278ef20a2b97248277b07a7b58a063618d)
|
||||
- Fixed bug [`7be211f`](https://git.odit.services/lfk/document-server/commit/7be211f8b7b26f7f620df81af4ebde5eec2feec2)
|
||||
|
||||
#### [v0.3.0](https://git.odit.services/lfk/document-server/compare/v0.2.0...v0.3.0)
|
||||
|
||||
- 🚀Bumped version to v0.3.0 [`449a96b`](https://git.odit.services/lfk/document-server/commit/449a96b3027fe93d8042b30420245f66e92f14b8)
|
||||
- Merge pull request 'Card generation feature/14-card_generation' (#24) from feature/14-card_generation into dev [`703eaa0`](https://git.odit.services/lfk/document-server/commit/703eaa0e9d667b628eab4e8496689fe66238f896)
|
||||
> 12 February 2021
|
||||
|
||||
- Merge pull request 'Alpha Release 0.3.0 - Runnercard generation' (#25) from dev into main [`406add3`](https://git.odit.services/lfk/document-server/commit/406add3d517473d01628b6405569de6cb85114e0)
|
||||
- 🚀Bumped version to v0.2.0 [`491cdb8`](https://git.odit.services/lfk/document-server/commit/491cdb8d71a80ea196d16334c0c80b8f7cc859c5)
|
||||
- Added card generation speed tests (part 1) [`68572b1`](https://git.odit.services/lfk/document-server/commit/68572b194eb740238be8101efed6fdb2a207f65b)
|
||||
- Implemented first experimental speedtest [`e3a45a6`](https://git.odit.services/lfk/document-server/commit/e3a45a61ac3b2d691c2f75d36155896b7ed301d8)
|
||||
- Added basic logic to generate two-sided runnercards [`d3a213c`](https://git.odit.services/lfk/document-server/commit/d3a213ce3326aeb96d924e16a31fc87bf82eb5b3)
|
||||
- 🧾New changelog file version [CI SKIP] [skip ci] [`149bf18`](https://git.odit.services/lfk/document-server/commit/149bf1849db20b863ec998a72c77559ec401bc32)
|
||||
- Fixed double-sided printing [`7f58dd6`](https://git.odit.services/lfk/document-server/commit/7f58dd694b53152069c2095b2e18dd3a46cd04dd)
|
||||
- 🧾New changelog file version [CI SKIP] [skip ci] [`e74b9a4`](https://git.odit.services/lfk/document-server/commit/e74b9a4c9d041780e0cfd8c4d68a5b63f916e091)
|
||||
- Added basic card generation function [`8fc6c71`](https://git.odit.services/lfk/document-server/commit/8fc6c7176ee92f813db1e1d4b3e5ef1b2f4e1aef)
|
||||
- Beautified output a bit [`aefe549`](https://git.odit.services/lfk/document-server/commit/aefe5493b06c04cc2b20029e1f7fc5f15ec9c04e)
|
||||
- Added barcode generatin [`5c075bc`](https://git.odit.services/lfk/document-server/commit/5c075bce8b94ff4482448c3cd56bdc28cbe0a7d9)
|
||||
|
@ -25,6 +38,8 @@ All notable changes to this project will be documented in this file. Dates are d
|
|||
- Fixed runnercard backside padding [`08e8587`](https://git.odit.services/lfk/document-server/commit/08e858726c1462b599ba9cb3f7fb057f35178b83)
|
||||
- Added sizing for the real cards [`b92a6f7`](https://git.odit.services/lfk/document-server/commit/b92a6f7b2b98fb0074d5a563d9918295e9ec0274)
|
||||
- 🧾New changelog file version [CI SKIP] [skip ci] [`8a90f63`](https://git.odit.services/lfk/document-server/commit/8a90f63b0919376beefef6a52aae9a59337aea59)
|
||||
- 🚀Bumped version to v0.3.0 [`449a96b`](https://git.odit.services/lfk/document-server/commit/449a96b3027fe93d8042b30420245f66e92f14b8)
|
||||
- Merge pull request 'Card generation feature/14-card_generation' (#24) from feature/14-card_generation into dev [`703eaa0`](https://git.odit.services/lfk/document-server/commit/703eaa0e9d667b628eab4e8496689fe66238f896)
|
||||
- Added speedtest script to package [`75b8b28`](https://git.odit.services/lfk/document-server/commit/75b8b281b87d9b173093f16beae12d707ec05052)
|
||||
- Fixed bug in array swapping function [`9697d53`](https://git.odit.services/lfk/document-server/commit/9697d53a1527854536f8ddf5426f7ca902772f51)
|
||||
- Added **very** basic backside [`68f46a4`](https://git.odit.services/lfk/document-server/commit/68f46a45b5a51c8a8edafca852cb274af388fa76)
|
||||
|
|
|
@ -35,6 +35,7 @@ The basic generation mechanism makes the templates and routes interchangeable (i
|
|||
| CURRENCY_SYMBOL | String | "€" | The your currency's symbol - used to generate pdf text.
|
||||
| SPONSORING_RECEIPT_MINIMUM_AMOUNT | String | "10" | The mimimum total donation amount a sponsor has to donate to be able to receive a donation receipt - used to generate pdf text.
|
||||
| SPONOR_LOGOS | Array<String> | Empty png | The sponsor images you want to loop through. You can provide them via http url, local file or base64-encoded image.
|
||||
| API_KEY | String(min length: 64) | Random generated string | The api key you want to use for auth (query-param `key`), has to be at least 64 chars long.
|
||||
|
||||
## Templates
|
||||
> The document server uses html templates to generate various pdf documents.
|
||||
|
|
24
licenses.md
24
licenses.md
|
@ -698,12 +698,32 @@ If the Work includes a "NOTICE" text file as part of its distribution, then any
|
|||
END OF TERMS AND CONDITIONS
|
||||
|
||||
# routing-controllers
|
||||
**Author**: [object Object]
|
||||
**Author**: TypeStack contributors
|
||||
**Repo**: [object Object]
|
||||
**License**: MIT
|
||||
**Description**: Create structured, declarative and beautifully organized class-based controllers with heavy decorators usage for Express / Koa using TypeScript.
|
||||
## License Text
|
||||
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2015-2020 TypeStack
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
# routing-controllers-openapi
|
||||
**Author**: Aleksi Pekkala <aleksipekkala@gmail.com>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@odit/lfk-document-server",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"description": "The document generation server for the LfK! runner system. This generates certificates, sponsoring aggreements and more",
|
||||
"main": "src/app.ts",
|
||||
"scripts": {
|
||||
|
|
|
@ -1,25 +1,33 @@
|
|||
import { MetadataArgsStorage } from 'routing-controllers';
|
||||
import { routingControllersToSpec } from 'routing-controllers-openapi';
|
||||
import { config } from './config';
|
||||
|
||||
/**
|
||||
* This function generates a the openapi spec from route metadata and type schemas.
|
||||
* @param storage MetadataArgsStorage object generated by routing-controllers.
|
||||
* @param schemas MetadataArgsStorage object generated by class-validator-jsonschema.
|
||||
*/
|
||||
export function generateSpec(storage: MetadataArgsStorage, schemas) {
|
||||
return routingControllersToSpec(
|
||||
storage,
|
||||
{},
|
||||
{
|
||||
components: {
|
||||
schemas,
|
||||
},
|
||||
info: {
|
||||
description: "The the API for the LfK! document server.",
|
||||
title: "LfK! document server API",
|
||||
version: config.version
|
||||
},
|
||||
}
|
||||
);
|
||||
import { MetadataArgsStorage } from 'routing-controllers';
|
||||
import { routingControllersToSpec } from 'routing-controllers-openapi';
|
||||
import { config } from './config';
|
||||
|
||||
/**
|
||||
* This function generates a the openapi spec from route metadata and type schemas.
|
||||
* @param storage MetadataArgsStorage object generated by routing-controllers.
|
||||
* @param schemas MetadataArgsStorage object generated by class-validator-jsonschema.
|
||||
*/
|
||||
export function generateSpec(storage: MetadataArgsStorage, schemas) {
|
||||
return routingControllersToSpec(
|
||||
storage,
|
||||
{},
|
||||
{
|
||||
components: {
|
||||
schemas,
|
||||
"securitySchemes": {
|
||||
"AuthToken": {
|
||||
"type": "apiKey",
|
||||
"in": "query",
|
||||
"name": "key",
|
||||
description: "A simple api key. See the README's env section for more details."
|
||||
}
|
||||
}
|
||||
},
|
||||
info: {
|
||||
description: "The the API for the LfK! document server.",
|
||||
title: "LfK! document server API",
|
||||
version: config.version
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
60
src/app.ts
60
src/app.ts
|
@ -1,29 +1,31 @@
|
|||
import consola from "consola";
|
||||
import "reflect-metadata";
|
||||
import { createExpressServer } from "routing-controllers";
|
||||
import { config, e as errors } from './config';
|
||||
import loaders from "./loaders/index";
|
||||
import { ErrorHandler } from './middlewares/ErrorHandler';
|
||||
|
||||
const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts';
|
||||
const app = createExpressServer({
|
||||
middlewares: [ErrorHandler],
|
||||
development: config.development,
|
||||
cors: true,
|
||||
controllers: [`${__dirname}/controllers/*.${CONTROLLERS_FILE_EXTENSION}`],
|
||||
});
|
||||
|
||||
async function main() {
|
||||
await loaders(app);
|
||||
app.listen(config.internal_port, () => {
|
||||
consola.success(
|
||||
`⚡️[server]: Server is running at http://localhost:${config.internal_port}`
|
||||
);
|
||||
});
|
||||
}
|
||||
if (errors === 0) {
|
||||
main();
|
||||
} else {
|
||||
consola.error("error");
|
||||
// something's wrong
|
||||
}
|
||||
import consola from "consola";
|
||||
import "reflect-metadata";
|
||||
import { createExpressServer } from "routing-controllers";
|
||||
import { config, e as errors } from './config';
|
||||
import loaders from "./loaders/index";
|
||||
import AuthChecker from './middlewares/AuthChecker';
|
||||
import { ErrorHandler } from './middlewares/ErrorHandler';
|
||||
|
||||
const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts';
|
||||
const app = createExpressServer({
|
||||
middlewares: [ErrorHandler],
|
||||
authorizationChecker: AuthChecker,
|
||||
development: config.development,
|
||||
cors: true,
|
||||
controllers: [`${__dirname}/controllers/*.${CONTROLLERS_FILE_EXTENSION}`],
|
||||
});
|
||||
|
||||
async function main() {
|
||||
await loaders(app);
|
||||
app.listen(config.internal_port, () => {
|
||||
consola.success(
|
||||
`⚡️[server]: Server is running at http://localhost:${config.internal_port}`
|
||||
);
|
||||
});
|
||||
}
|
||||
if (errors === 0) {
|
||||
main();
|
||||
} else {
|
||||
consola.error("error");
|
||||
// something's wrong
|
||||
}
|
||||
|
|
|
@ -1,30 +1,52 @@
|
|||
import { config as configDotenv } from 'dotenv';
|
||||
|
||||
configDotenv();
|
||||
export const config = {
|
||||
internal_port: parseInt(process.env.APP_PORT) || 4010,
|
||||
development: process.env.NODE_ENV === "production",
|
||||
version: process.env.VERSION || require('../package.json').version,
|
||||
eventname: process.env.EVENT_NAME || "Please set the event name",
|
||||
currency_symbol: process.env.CURRENCY_SYMBOL || "€",
|
||||
sponsoring_receipt_minimum_amount: process.env.SPONSORING_RECEIPT_MINIMUM_AMOUNT || "10",
|
||||
codeformat: process.env.CODEFORMAT || "qrcode",
|
||||
sponor_logos: getSponsorLogos()
|
||||
}
|
||||
let errors = 0
|
||||
if (typeof config.internal_port !== "number") {
|
||||
errors++
|
||||
}
|
||||
if (typeof config.development !== "boolean") {
|
||||
errors++
|
||||
}
|
||||
function getSponsorLogos(): string[] {
|
||||
try {
|
||||
const logos = JSON.parse(process.env.SPONOR_LOGOS);
|
||||
if (!Array.isArray(logos)) { throw new Error("Not an array.") }
|
||||
return logos;
|
||||
} catch (error) {
|
||||
return ["data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg=="];
|
||||
}
|
||||
}
|
||||
import consola from "consola";
|
||||
import { config as configDotenv } from 'dotenv';
|
||||
|
||||
configDotenv();
|
||||
export const config = {
|
||||
internal_port: parseInt(process.env.APP_PORT) || 4010,
|
||||
development: process.env.NODE_ENV === "production",
|
||||
version: process.env.VERSION || require('../package.json').version,
|
||||
eventname: process.env.EVENT_NAME || "Please set the event name",
|
||||
currency_symbol: process.env.CURRENCY_SYMBOL || "€",
|
||||
sponsoring_receipt_minimum_amount: process.env.SPONSORING_RECEIPT_MINIMUM_AMOUNT || "10",
|
||||
codeformat: process.env.CODEFORMAT || "qrcode",
|
||||
sponor_logos: getSponsorLogos(),
|
||||
api_key: getApiKey(),
|
||||
}
|
||||
let errors = 0
|
||||
if (typeof config.internal_port !== "number") {
|
||||
errors++
|
||||
}
|
||||
if (typeof config.development !== "boolean") {
|
||||
errors++
|
||||
}
|
||||
function getSponsorLogos(): string[] {
|
||||
try {
|
||||
const logos = JSON.parse(process.env.SPONOR_LOGOS);
|
||||
if (!Array.isArray(logos)) { throw new Error("Not an array.") }
|
||||
return logos;
|
||||
} catch (error) {
|
||||
return ["data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg=="];
|
||||
}
|
||||
}
|
||||
|
||||
function getApiKey(): string {
|
||||
const key = process.env.API_KEY;
|
||||
if (!key) {
|
||||
consola.info("No API key set - generating a random one...");
|
||||
let result = '';
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const charactersLength = characters.length;
|
||||
for (var i = 0; i < 64; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
consola.info(`API KEY: ${result}`)
|
||||
return result;
|
||||
}
|
||||
if (key.length < 64) {
|
||||
consola.error(`API key is too short - minimum: 64, current: ${key.length}`)
|
||||
throw new Error("API_KEY too short.")
|
||||
}
|
||||
return key
|
||||
}
|
||||
export let e = errors
|
|
@ -1,46 +1,48 @@
|
|||
import { Body, JsonController, Post, QueryParam, Res } from 'routing-controllers';
|
||||
import { OpenAPI } from 'routing-controllers-openapi';
|
||||
import { Runner } from '../models/Runner';
|
||||
import { RunnerCard } from '../models/RunnerCard';
|
||||
import { PdfCreator } from '../PdfCreator';
|
||||
|
||||
/**
|
||||
* The pdf controller handels all endpoints concerning pdf generation.
|
||||
* It therefore is the hearth of the document-generation server's endpoints.
|
||||
* All endpoints have to accept a locale query-param to support i18n.
|
||||
*/
|
||||
@JsonController()
|
||||
export class PdfController {
|
||||
private pdf: PdfCreator = new PdfCreator();
|
||||
private initialized: boolean = false;
|
||||
|
||||
@Post('/contracts')
|
||||
@OpenAPI({ description: "Generate Sponsoring contract pdfs from runner objects.<br>You can choose your prefered locale by passing the 'locale' query-param.<br> If you provide more than 100 runenrs this could take a moment or two (we tested up to 1000 runners in about 70sec so far)." })
|
||||
async generateContracts(@Body({ validate: true, options: { limit: "500mb" } }) runners: Runner | Runner[], @Res() res: any, @QueryParam("locale") locale: string, @QueryParam("codeformat") codeformat: string) {
|
||||
if (!this.initialized) {
|
||||
await this.pdf.init();
|
||||
this.initialized = true;
|
||||
}
|
||||
if (!Array.isArray(runners)) {
|
||||
runners = [runners];
|
||||
}
|
||||
const contracts = await this.pdf.generateSponsoringContract(runners, locale, codeformat);
|
||||
res.setHeader('content-type', 'application/pdf');
|
||||
return contracts;
|
||||
}
|
||||
|
||||
@Post('/cards')
|
||||
@OpenAPI({ description: "Generate runner card pdfs from runner card objects.<br>You can choose your prefered locale by passing the 'locale' query-param." })
|
||||
async generateCards(@Body({ validate: true, options: { limit: "500mb" } }) cards: RunnerCard | RunnerCard[], @Res() res: any, @QueryParam("locale") locale: string) {
|
||||
if (!this.initialized) {
|
||||
await this.pdf.init();
|
||||
this.initialized = true;
|
||||
}
|
||||
if (!Array.isArray(cards)) {
|
||||
cards = [cards];
|
||||
}
|
||||
const contracts = await this.pdf.generateRunnerCards(cards, locale);
|
||||
res.setHeader('content-type', 'application/pdf');
|
||||
return contracts;
|
||||
}
|
||||
}
|
||||
import { Authorized, Body, JsonController, Post, QueryParam, Res } from 'routing-controllers';
|
||||
import { OpenAPI } from 'routing-controllers-openapi';
|
||||
import { Runner } from '../models/Runner';
|
||||
import { RunnerCard } from '../models/RunnerCard';
|
||||
import { PdfCreator } from '../PdfCreator';
|
||||
|
||||
/**
|
||||
* The pdf controller handels all endpoints concerning pdf generation.
|
||||
* It therefore is the hearth of the document-generation server's endpoints.
|
||||
* All endpoints have to accept a locale query-param to support i18n.
|
||||
*/
|
||||
@JsonController()
|
||||
@Authorized()
|
||||
@OpenAPI({ security: [{ "AuthToken": [] }] })
|
||||
export class PdfController {
|
||||
private pdf: PdfCreator = new PdfCreator();
|
||||
private initialized: boolean = false;
|
||||
|
||||
@Post('/contracts')
|
||||
@OpenAPI({ description: "Generate Sponsoring contract pdfs from runner objects.<br>You can choose your prefered locale by passing the 'locale' query-param.<br> If you provide more than 100 runenrs this could take a moment or two (we tested up to 1000 runners in about 70sec so far)." })
|
||||
async generateContracts(@Body({ validate: true, options: { limit: "500mb" } }) runners: Runner | Runner[], @Res() res: any, @QueryParam("locale") locale: string, @QueryParam("codeformat") codeformat: string) {
|
||||
if (!this.initialized) {
|
||||
await this.pdf.init();
|
||||
this.initialized = true;
|
||||
}
|
||||
if (!Array.isArray(runners)) {
|
||||
runners = [runners];
|
||||
}
|
||||
const contracts = await this.pdf.generateSponsoringContract(runners, locale, codeformat);
|
||||
res.setHeader('content-type', 'application/pdf');
|
||||
return contracts;
|
||||
}
|
||||
|
||||
@Post('/cards')
|
||||
@OpenAPI({ description: "Generate runner card pdfs from runner card objects.<br>You can choose your prefered locale by passing the 'locale' query-param." })
|
||||
async generateCards(@Body({ validate: true, options: { limit: "500mb" } }) cards: RunnerCard | RunnerCard[], @Res() res: any, @QueryParam("locale") locale: string) {
|
||||
if (!this.initialized) {
|
||||
await this.pdf.init();
|
||||
this.initialized = true;
|
||||
}
|
||||
if (!Array.isArray(cards)) {
|
||||
cards = [cards];
|
||||
}
|
||||
const contracts = await this.pdf.generateRunnerCards(cards, locale);
|
||||
res.setHeader('content-type', 'application/pdf');
|
||||
return contracts;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { Action } from "routing-controllers";
|
||||
import { config } from '../config';
|
||||
|
||||
/**
|
||||
* Handles authentication via jwt's (Bearer authorization header) for all api endpoints using the @Authorized decorator.
|
||||
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
|
||||
* @param permissions The permissions that the endpoint using @Authorized requires.
|
||||
*/
|
||||
const AuthChecker = async (action: Action) => {
|
||||
const provided_token = action.request.query.key;
|
||||
return provided_token == config.api_key;
|
||||
}
|
||||
|
||||
export default AuthChecker
|
Loading…
Reference in New Issue