Merge pull request #5 from auth-tools/dev/1-import_packages

Dev/1 import packages
This commit is contained in:
Laurenz Rausche 2024-05-09 23:20:23 +02:00 committed by GitHub
commit 6f2a6f63f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 1972 additions and 1 deletions

21
packages/base/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 @auth-tools
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.

View File

View File

@ -0,0 +1,29 @@
{
"name": "@auth-tools/base",
"version": "0.0.1-alpha.2",
"description": "A structured authentication protocol for Javascript. (base)",
"main": "dist/index.js",
"repository": "https://github.com/auth-tools/auth-tools",
"author": "Laurenz Rausche <laurenz@laurenz-rausche.de>",
"license": "MIT",
"private": false,
"scripts": {
"build": "npm run build:remove && tsc",
"build:remove": "rimraf dist",
"local": "npm run build && ts-node-dev --respawn local/index.ts",
"prepublish": "npm run build",
"remove": "npm run build:remove && rimraf node_modules yarn.lock package-lock.json pnpm-lock.yaml"
},
"dependencies": {},
"devDependencies": {
"@auth-tools/base": "link:.",
"@auth-tools/logger": "^0.0.1-alpha.1",
"rimraf": "^5.0.5",
"ts-node-dev": "^2.0.0",
"typescript": "^5.4.5"
},
"peerDependencies": {},
"files": [
"dist"
]
}

73
packages/base/src/auth.ts Normal file
View File

@ -0,0 +1,73 @@
import {
InterceptEventCallback,
InterceptEventCallbacks,
InterceptEvents,
InterceptEventsDefinition,
} from "./events/intercept";
import {
UseEventCallback,
UseEventCallbacks,
UseEvents,
UseEventsDefinition,
} from "./events/use";
import { DeepRequired } from "./helpers";
import { LogFunction } from "@auth-tools/logger";
//definition of internal data of AuthBase class
export type AuthInternal<
AuthConfig,
ClassUseEvents extends UseEventsDefinition,
ClassInterceptEvents extends InterceptEventsDefinition
> = {
config: DeepRequired<AuthConfig>;
log: LogFunction;
useEventCallbacks: UseEventCallbacks<ClassUseEvents>;
interceptEventCallbacks: InterceptEventCallbacks<ClassInterceptEvents>;
};
//auth base class
export class AuthBase<
AuthConfig,
ClassUseEvents extends UseEventsDefinition,
ClassInterceptEvents extends InterceptEventsDefinition
> {
//internal auth data
public _internal: AuthInternal<
AuthConfig,
ClassUseEvents,
ClassInterceptEvents
>;
constructor(
config: DeepRequired<AuthConfig>,
log: LogFunction,
defaultUseEvents: UseEventCallbacks<ClassUseEvents>,
defaultInterceptEvents: InterceptEventCallbacks<ClassInterceptEvents>
) {
//sets _internal
this._internal = {
config: config,
log: log,
useEventCallbacks: defaultUseEvents,
interceptEventCallbacks: defaultInterceptEvents,
};
}
//sets a use event
public use<UseEventName extends keyof UseEvents<ClassUseEvents>>(
event: UseEventName,
callback: UseEventCallback<ClassUseEvents, UseEventName>
): void {
this._internal.useEventCallbacks[event] = callback;
}
//sets a intercept event
public intercept<
InterceptEventName extends keyof InterceptEvents<ClassInterceptEvents>
>(
event: InterceptEventName,
callback: InterceptEventCallback<ClassInterceptEvents, InterceptEventName>
): void {
this._internal.interceptEventCallbacks[event] = callback;
}
}

View File

@ -0,0 +1,50 @@
import { Promisify } from "../index";
//definition for a intercept event (only used for auto completion with extends)
type InterceptEventDefinition<Data extends {} = {}> = {
data: Data;
};
//definition for intercept events (only used for auto completion with extends)
export type InterceptEventsDefinition = {
[InterceptEventName: string]: InterceptEventDefinition;
};
//data for an intercept event
type InterceptEvent<ClassInterceptEvents extends InterceptEventDefinition> = {
data: ClassInterceptEvents["data"];
return: {
serverError?: boolean;
intercepted: boolean;
interceptCode: number;
};
};
//all intercept events
export type InterceptEvents<
ClassInterceptEvents extends InterceptEventsDefinition
> = {
[InterceptEventName in keyof ClassInterceptEvents]: InterceptEvent<
ClassInterceptEvents[InterceptEventName]
>;
};
//constructed callback for intercept event
export type InterceptEventCallback<
ClassInterceptEvents extends InterceptEventsDefinition,
InterceptEventName extends keyof InterceptEvents<ClassInterceptEvents>
> = (
data: InterceptEvents<ClassInterceptEvents>[InterceptEventName]["data"]
) => Promisify<
InterceptEvents<ClassInterceptEvents>[InterceptEventName]["return"]
>;
//all callbacks for intercept events
export type InterceptEventCallbacks<
ClassInterceptEvents extends InterceptEventsDefinition
> = {
[InterceptEventName in keyof InterceptEvents<ClassInterceptEvents>]: InterceptEventCallback<
ClassInterceptEvents,
InterceptEventName
>;
};

View File

@ -0,0 +1,41 @@
import { Promisify } from "../index";
//definition for a use event (only used for auto completion with extends)
type UseEventDefinition<Data extends {} = {}, Return extends {} = {}> = {
data: Data;
return: Return;
};
//definition for use events (only used for auto completion with extends)
export type UseEventsDefinition = {
[UseEventName: string]: UseEventDefinition;
};
//data for an use event
type UseEvent<ClassUseEvents extends UseEventDefinition> = {
data: ClassUseEvents["data"];
return: { serverError?: boolean } & ClassUseEvents["return"];
};
//all use events
export type UseEvents<ClassUseEvents extends UseEventsDefinition> = {
[UseEventName in keyof ClassUseEvents]: UseEvent<
ClassUseEvents[UseEventName]
>;
};
//constructed callback for use event
export type UseEventCallback<
ClassUseEvents extends UseEventsDefinition,
UseEventName extends keyof UseEvents<ClassUseEvents>
> = (
data: UseEvents<ClassUseEvents>[UseEventName]["data"]
) => Promisify<UseEvents<ClassUseEvents>[UseEventName]["return"]>;
//all callbacks for use events
export type UseEventCallbacks<ClassUseEvents extends UseEventsDefinition> = {
[UseEventName in keyof UseEvents<ClassUseEvents>]: UseEventCallback<
ClassUseEvents,
UseEventName
>;
};

View File

@ -0,0 +1,12 @@
import { User } from "./protocol";
export type Promisify<Type> = Promise<Type> | Type;
export type DeepRequired<T> = { [K in keyof T]-?: DeepRequired<T[K]> };
export type DeepOptional<T> = { [K in keyof T]?: DeepRequired<T[K]> };
export type KeysStartingWith<Type, Str extends string> = {
[Key in keyof Type as Key extends `${Str}_${infer _}`
? Key
: never]: Type[Key];
};
export type UserData = User<"id" | "email" | "username" | "hashedPassword">;
export type Payload = User<"id">;

View File

@ -0,0 +1,13 @@
export { AuthBase } from "./auth";
export type { AuthInternal } from "./auth";
export type { InterceptEventCallbacks } from "./events/intercept";
export type { UseEventCallbacks } from "./events/use";
export type {
DeepOptional,
DeepRequired,
Payload,
Promisify,
UserData,
} from "./helpers";
export type { AuthProtocol, AuthRequest, AuthResponse, User } from "./protocol";
export type { AuthMessages } from "./protocol/messages";

View File

@ -0,0 +1,169 @@
import { DeepOptional, KeysStartingWith } from "../helpers";
import { AuthMessages } from "./messages";
//all possible method names
type AuthMethodStrings =
| "server"
| "validate"
| "register"
| "login"
| "logout"
| "refresh"
| "check";
//type builder for an auth response
type AuthResponseBuilder<
Method extends AuthMethodStrings,
StatusCode extends number,
ResponseData extends any,
InterceptCode extends number = 0,
AuthMessage = AuthMessages[`${Method}_${StatusCode}`]
> = {
auth: {
error: StatusCode extends 0 ? false : true;
errorType: Method extends "server" ? "server" : "method";
message: AuthMessage;
codes: {
status: StatusCode;
intercept: InterceptCode;
};
};
data: ResponseData;
};
//definition for argument for an auth response builder
type AuthMethodResponsesDefinition<Method extends AuthMethodStrings> = {
[ResponseName in keyof KeysStartingWith<
AuthMessages,
Method
>]: AuthResponseBuilder<Method, number, any, number>;
};
//type that builds all responses in one object
type AuthMethodResponses<
Method extends AuthMethodStrings,
Responses extends AuthMethodResponsesDefinition<Method>
> = {
[ResponseName in keyof Responses]: Responses[ResponseName];
} & {
server_5: AuthResponseBuilder<"server", 5, null>;
};
//type that builds an entire method
type AuthMethodBuilder<
Method extends AuthMethodStrings,
Request extends any,
Responses extends AuthMethodResponsesDefinition<Method>
> = {
request: DeepOptional<Request>;
responses: AuthMethodResponses<Method, Responses>;
};
//all data of a user
type UserData = {
id: string;
login: string;
email: string;
username: string;
password: string;
hashedPassword: string;
accessToken: string;
refreshToken: string;
};
//a user map
export type User<Keys extends keyof UserData = never> = Pick<UserData, Keys>;
//all auth methods
export type AuthProtocol = {
validate: AuthMethodBuilder<
"validate",
User<"accessToken">,
{
validate_0: AuthResponseBuilder<"validate", 0, User<"id">>;
validate_1: AuthResponseBuilder<"validate", 1, null>;
validate_2: AuthResponseBuilder<"validate", 2, null>;
validate_3: AuthResponseBuilder<"validate", 3, null>;
validate_9: AuthResponseBuilder<"validate", 9, null, number>;
}
>;
register: AuthMethodBuilder<
"register",
User<"email" | "username" | "password">,
{
register_0: AuthResponseBuilder<
"register",
0,
User<"id" | "email" | "username">
>;
register_1: AuthResponseBuilder<"register", 1, null>;
register_2: AuthResponseBuilder<"register", 2, null>;
register_3: AuthResponseBuilder<"register", 3, null>;
register_4: AuthResponseBuilder<"register", 4, null>;
register_5: AuthResponseBuilder<"register", 5, null>;
register_6: AuthResponseBuilder<"register", 6, null>;
register_7: AuthResponseBuilder<"register", 7, null>;
register_9: AuthResponseBuilder<"register", 9, null, number>;
}
>;
login: AuthMethodBuilder<
"login",
User<"login" | "password">,
{
login_0: AuthResponseBuilder<
"login",
0,
User<"accessToken" | "refreshToken">
>;
login_1: AuthResponseBuilder<"login", 1, null>;
login_2: AuthResponseBuilder<"login", 2, null>;
login_3: AuthResponseBuilder<"login", 3, null>;
login_4: AuthResponseBuilder<"login", 4, null>;
login_5: AuthResponseBuilder<"login", 5, null>;
login_9: AuthResponseBuilder<"login", 9, null, number>;
}
>;
logout: AuthMethodBuilder<
"logout",
User<"refreshToken">,
{
logout_0: AuthResponseBuilder<"logout", 0, null>;
logout_1: AuthResponseBuilder<"logout", 1, null>;
logout_2: AuthResponseBuilder<"logout", 2, null>;
logout_3: AuthResponseBuilder<"logout", 3, null>;
logout_4: AuthResponseBuilder<"logout", 4, null>;
logout_9: AuthResponseBuilder<"logout", 9, null, number>;
}
>;
refresh: AuthMethodBuilder<
"refresh",
User<"refreshToken">,
{
refresh_0: AuthResponseBuilder<"refresh", 0, User<"accessToken">>;
refresh_1: AuthResponseBuilder<"refresh", 1, null>;
refresh_2: AuthResponseBuilder<"refresh", 2, null>;
refresh_3: AuthResponseBuilder<"refresh", 3, null>;
refresh_4: AuthResponseBuilder<"refresh", 4, null>;
refresh_9: AuthResponseBuilder<"refresh", 9, null, number>;
}
>;
check: AuthMethodBuilder<
"check",
User<"accessToken" | "refreshToken">,
{
check_0: AuthResponseBuilder<"check", 0, null>;
check_1: AuthResponseBuilder<"check", 1, null>;
check_2: AuthResponseBuilder<"check", 2, null>;
check_3: AuthResponseBuilder<"check", 3, null>;
check_4: AuthResponseBuilder<"check", 4, null>;
check_5: AuthResponseBuilder<"check", 5, null>;
check_9: AuthResponseBuilder<"check", 9, null, number>;
}
>;
};
export type AuthRequest<Method extends Exclude<AuthMethodStrings, "server">> =
AuthProtocol[Method]["request"];
export type AuthResponse<Method extends Exclude<AuthMethodStrings, "server">> =
AuthProtocol[Method]["responses"][keyof AuthProtocol[Method]["responses"]];

View File

@ -0,0 +1,44 @@
export type AuthMessages = {
[Key: `${string}_${number}`]: string;
server_5: "An error occurred on the server. Please try again later.";
validate_0: "Validation successful.";
validate_1: "The validation method is disabled.";
validate_2: 'The "accessToken" is missing.';
validate_3: 'The "accessToken" is invalid.';
validate_9: "The validation request was intercepted.";
register_0: "Registration successful.";
register_1: "The registration method is disabled.";
register_2: 'The "email", "username" or "password" is missing.';
register_3: 'The "email" is malformed.';
register_4: 'The "password" is too weak.';
register_5: 'The "email" is already in use.';
register_6: 'The "username" is already in use.';
register_7: 'The "login" is already in use.';
register_9: "The registration request was intercepted.";
login_0: "Login successful.";
login_1: "The login method is disabled.";
login_2: 'The "login" ("email" or "username") or "password" is missing.';
login_3: "The user was not found.";
login_4: 'The "password" is incorrect.';
login_5: 'The user was not found or the "password" is incorrect.';
login_9: "The login request was intercepted.";
logout_0: "Logout successful.";
logout_1: "The logout method is disabled.";
logout_2: 'The "refreshToken" is missing.';
logout_3: 'The "refreshToken" is invalid.';
logout_4: 'The "refreshToken" does not exist.';
logout_9: "The logout request was intercepted.";
refresh_0: "Refresh successful.";
refresh_1: "The refresh method is disabled.";
refresh_2: 'The "refreshToken" is missing.';
refresh_3: 'The "refreshToken" is invalid.';
refresh_4: 'The "refreshToken" does not exist.';
refresh_9: "The refresh request was intercepted.";
check_0: "Check successful.";
check_1: "The check method is disabled.";
check_2: 'The "accessToken" or "refreshToken" is missing.';
check_3: 'The "refreshToken" is invalid.';
check_4: 'The "refreshToken" does not exist.';
check_5: 'The "accessToken" is invalid.';
check_9: "The check request was intercepted.";
};

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2016",
"lib": ["ES2016"],
"module": "CommonJS",
"moduleResolution": "Node",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}

21
packages/client/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 @auth-tools
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.

View File

View File

@ -0,0 +1,28 @@
{
"name": "@auth-tools/client",
"version": "0.0.0",
"description": "A structured authentication protocol for Javascript. (client)",
"main": "dist/index.js",
"repository": "https://github.com/auth-tools/auth-tools",
"author": "Laurenz Rausche <laurenz@laurenz-rausche.de>",
"license": "MIT",
"private": false,
"scripts": {
"build": "npm run build:remove && tsc",
"build:remove": "rimraf dist",
"local": "npm run build && ts-node-dev --respawn local/index.ts",
"prepublish": "npm run build",
"remove": "npm run build:remove && rimraf node_modules yarn.lock package-lock.json pnpm-lock.yaml"
},
"dependencies": {},
"devDependencies": {
"@auth-tools/client": "link:.",
"rimraf": "^5.0.5",
"ts-node-dev": "^2.0.0",
"typescript": "^5.4.5"
},
"peerDependencies": {},
"files": [
"dist"
]
}

View File

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2016",
"lib": ["ES2016"],
"module": "CommonJS",
"moduleResolution": "Node",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}

21
packages/logger/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 @auth-tools
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.

View File

@ -0,0 +1,66 @@
import DefaultLogger, { COLORS, Logger } from "@auth-tools/logger";
DefaultLogger.log("debug", `"debug" log with DefaultLogger`);
DefaultLogger.log("info", `"info" log with DefaultLogger`);
DefaultLogger.log("warn", `"warn" log with DefaultLogger`);
DefaultLogger.log("error", `"error" log with DefaultLogger`);
//set config after creation
DefaultLogger.setConfig({
disableColor: true,
formatString: "Color disabled: [%l] %d %m",
});
//log shorthands
DefaultLogger.debug(`"debug" log with DefaultLogger and updated config`);
DefaultLogger.info(`"info" log with DefaultLogger and updated config`);
DefaultLogger.warn(`"warn" log with DefaultLogger and updated config`);
DefaultLogger.error(`"error" log with DefaultLogger and updated config`);
//multiple arguments
DefaultLogger.log("error", `Argument 1`, "Argument 2");
//update config again
DefaultLogger.setConfig({ disableColor: false, formatString: "Built-In: %m" });
//builtin Methods
DefaultLogger.log("debug", DefaultLogger.color("Red", COLORS.error));
DefaultLogger.log("info", DefaultLogger.twoDigits("1"));
DefaultLogger.log("warn", DefaultLogger.currentTime());
DefaultLogger.log("error", DefaultLogger.currentDate());
//create custom logger instance
const logger2 = new Logger({
disableColor: false, //set to true to disable all colors
formatString: [
//all replacement vars:
"Vars: %t = HH:MM:SS",
"Vars: %d = YYYY-MM-DD",
"Vars: %L = LEVEL",
"Vars: %l = level",
"Vars: %m = message",
].join("\n"),
});
logger2.log("error", "Hallo");
//update config to disable debug and warn
logger2.setConfig({
formatString: "Disabled Methods: [%L] %t %m",
methods: {
debug: false,
warn: false,
},
});
logger2.debug("Shouldn't log");
logger2.info("Should log");
logger2.warn("Shouldn't log");
logger2.error("Should log");
//format string with current instance config
logger2.setConfig({
formatString: "Format String: [%m]",
});
const formatted = logger2.format("info", "MY CUSTOM STRING");
console.log(formatted);

View File

@ -0,0 +1,29 @@
{
"name": "@auth-tools/logger",
"version": "0.0.1-alpha.1",
"description": "A structured authentication protocol for Javascript. (logger)",
"main": "dist/index.js",
"repository": "https://github.com/auth-tools/auth-tools",
"author": "Laurenz Rausche <laurenz@laurenz-rausche.de>",
"license": "MIT",
"private": false,
"scripts": {
"build": "npm run build:remove && tsc",
"build:remove": "rimraf dist",
"local": "npm run build && ts-node-dev --respawn local/index.ts",
"prepublish": "npm run build",
"remove": "npm run build:remove && rimraf node_modules yarn.lock package-lock.json pnpm-lock.yaml"
},
"dependencies": {},
"devDependencies": {
"@auth-tools/logger": "link:.",
"@types/node": "^20.12.7",
"rimraf": "^5.0.5",
"ts-node-dev": "^2.0.0",
"typescript": "^5.4.5"
},
"peerDependencies": {},
"files": [
"dist"
]
}

View File

@ -0,0 +1,7 @@
import { Logger } from "./logger";
//create default logger instance
const DefaultLogger = new Logger();
export * from "./logger";
export default DefaultLogger;

View File

@ -0,0 +1,148 @@
//util types
type DeepRequired<T> = { [K in keyof T]-?: DeepRequired<T[K]> };
//types for the logger
export type LogLevels = "debug" | "info" | "warn" | "error";
export type LogFunction = (level: LogLevels, ...message: string[]) => void;
export type LogConfig = {
disableColor?: boolean;
formatString?: string;
methods?: {
[key in LogLevels]?: boolean;
};
};
//enum for all ansi colors
export enum COLORS {
time = "90;1",
date = "32;1",
debug = "35;1",
info = "36",
warn = "33",
error = "31;1",
}
export class Logger {
private config: DeepRequired<LogConfig>;
constructor(config?: LogConfig) {
//make sure log functions contexts are set to class
this.debug = this.debug.bind(this);
this.info = this.info.bind(this);
this.warn = this.warn.bind(this);
this.error = this.error.bind(this);
this.setConfig = this.setConfig.bind(this);
this.log = this.log.bind(this);
this.color = this.color.bind(this);
this.format = this.format.bind(this);
this.twoDigits = this.twoDigits.bind(this);
this.currentTime = this.currentTime.bind(this);
this.currentDate = this.currentDate.bind(this);
//set config with defaults
this.config = {
disableColor: config?.disableColor ?? false,
formatString: config?.formatString ?? "[%L] %t %m",
methods: {
debug: config?.methods?.debug ?? true,
info: config?.methods?.info ?? true,
warn: config?.methods?.warn ?? true,
error: config?.methods?.error ?? true,
},
};
}
//shorthand for Logger.log("debug", ...)
public debug(...messages: string[]): void {
this.log("debug", ...messages);
}
//shorthand for Logger.log("info", ...)
public info(...messages: string[]): void {
this.log("info", ...messages);
}
//shorthand for Logger.log("warn", ...)
public warn(...messages: string[]): void {
this.log("warn", ...messages);
}
//shorthand for Logger.log("error", ...)
public error(...messages: string[]): void {
this.log("error", ...messages);
}
//config after initial creation
public setConfig(config?: LogConfig): void {
this.config = {
disableColor: config?.disableColor ?? this.config.disableColor,
formatString: config?.formatString ?? this.config.formatString,
methods: {
debug: config?.methods?.debug ?? this.config.methods.debug,
info: config?.methods?.info ?? this.config.methods.info,
warn: config?.methods?.warn ?? this.config.methods.warn,
error: config?.methods?.error ?? this.config.methods.error,
},
};
}
//log the message
public log(level: LogLevels, ...messages: string[]): void {
messages.forEach((message) => {
if (this.config.methods[level])
console[level](this.format(level, message));
});
}
//color a string
public color(str: string, ansiColorValue: string): string {
return this.config.disableColor
? str
: `\x1b[${ansiColorValue}m${str}\x1b[0m`;
}
//format multi line logs
public format(level: LogLevels, message: string): string {
return message
.split("\n")
.map((line) => this.formatLine(level, line))
.join("\n");
}
public twoDigits(str: string): string {
return str.length === 1 ? `0${str}` : str;
}
//format current time as HH:MM:SS
public currentTime(): string {
const date = new Date();
return (
this.twoDigits(date.getHours().toString()) +
":" +
this.twoDigits(date.getMinutes().toString()) +
":" +
this.twoDigits(date.getSeconds().toString())
);
}
//format current date as YYYY-MM-DD
public currentDate(): string {
const date = new Date();
return (
this.twoDigits(date.getFullYear().toString()) +
"-" +
this.twoDigits(date.getMonth().toString()) +
"-" +
this.twoDigits(date.getDate().toString())
);
}
//format a single log line
private formatLine(level: LogLevels, line: string): string {
return this.config.formatString
.replace("%t", this.color(this.currentTime(), COLORS["time"])) //replace %t with current date (HH:MM:SS)
.replace("%d", this.color(this.currentDate(), COLORS["date"])) //replace %d with current date (YYYY-MM-DD)
.replace("%L", this.color(level.toUpperCase(), COLORS[level])) //replace %L (uppercase level) with given level
.replace("%l", this.color(level.toLowerCase(), COLORS[level])) //replace %l (lowercase level) with given level
.replace("%m", line); //replace %m (message) with given message
}
}

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2016",
"lib": ["ES2016"],
"module": "CommonJS",
"moduleResolution": "Node",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}

21
packages/server/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 @auth-tools
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.

View File

@ -0,0 +1,72 @@
import { UserData } from "@auth-tools/base";
import { Logger } from "@auth-tools/logger";
import { AuthServer, AuthServerConfig } from "@auth-tools/server";
const Users: UserData[] = [];
const Tokens: string[] = [];
const logger = new Logger();
const authServerConfig: AuthServerConfig = {
secrets: {
accessToken: "SECRET",
refreshToken: "SECRET",
},
};
const authServer = new AuthServer(authServerConfig, logger.log);
authServer.use("getUserByMail", ({ email }) => {
const user = Users.find((usr) => usr.email === email) || null;
return { serverError: false, user };
});
authServer.use("getUserByName", ({ username }) => {
const user = Users.find((usr) => usr.username === username) || null;
return { serverError: false, user };
});
authServer.use("storeUser", ({ user }) => {
Users.push(user);
return { serverError: false };
});
authServer.use("checkToken", ({ refreshToken }) => {
const exists = Tokens.includes(refreshToken);
return { serverError: false, exists };
});
authServer.use("storeToken", ({ refreshToken }) => {
Tokens.push(refreshToken);
return { serverError: false };
});
authServer.use("deleteToken", ({ refreshToken }) => {
Tokens.splice(Tokens.indexOf(refreshToken), 1);
return { serverError: false };
});
authServer.use("validateMail", ({ email }) => {
const isValid = email.includes("@");
return { serverError: false, isValid };
});
authServer.use("validatePassword", ({ password }) => {
const isValid = password.length >= 8;
return { serverError: false, isValid };
});
authServer.use("hashPassword", ({ password }) => {
const hashedPassword = password.split("").reverse().join("");
return { serverError: false, hashedPassword };
});
authServer.use("checkPassword", ({ password, hashedPassword }) => {
const matches = password.split("").reverse().join("") === hashedPassword;
return { serverError: false, matches };
});
authServer.use("genId", ({}) => {
const id = Users.length.toString();
return { serverError: false, id };
});

View File

@ -0,0 +1,33 @@
{
"name": "@auth-tools/server",
"version": "0.0.1-alpha.1",
"description": "A structured authentication protocol for Javascript. (server)",
"main": "dist/index.js",
"repository": "https://github.com/auth-tools/auth-tools",
"author": "Laurenz Rausche <laurenz@laurenz-rausche.de>",
"license": "MIT",
"private": false,
"scripts": {
"build": "npm run build:remove && tsc",
"build:remove": "rimraf dist",
"local": "npm run build && ts-node-dev --respawn local/index.ts",
"prepublish": "npm run build",
"remove": "npm run build:remove && rimraf node_modules yarn.lock package-lock.json pnpm-lock.yaml"
},
"dependencies": {
"@auth-tools/base": "^0.0.1-alpha.2",
"jsonwebtoken": "^9.0.2"
},
"devDependencies": {
"@auth-tools/logger": "^0.0.1-alpha.1",
"@auth-tools/server": "link:.",
"@types/jsonwebtoken": "^9.0.6",
"rimraf": "^5.0.5",
"ts-node-dev": "^2.0.0",
"typescript": "^5.4.5"
},
"peerDependencies": {},
"files": [
"dist"
]
}

227
packages/server/src/auth.ts Normal file
View File

@ -0,0 +1,227 @@
import {
AuthBase,
AuthProtocol,
AuthRequest,
AuthResponse,
DeepRequired,
InterceptEventCallbacks,
UseEventCallbacks,
User,
} from "@auth-tools/base";
import { LogFunction } from "@auth-tools/logger";
import { undefinedInterceptEvent, undefinedUseEvent } from "./events";
import { createRegister } from "./methods/register";
import { createLogin } from "./methods/login";
import { createLogout } from "./methods/logout";
import { createRefresh } from "./methods/refresh";
import { createCheck } from "./methods/check";
import { createValidate } from "./methods/validate";
//states of a method
type MethodState = "active" | "disabled" | "removed";
//config passed by user to class
export type AuthServerConfig = {
secrets: {
accessToken: string;
refreshToken: string;
};
expiresIn?: number;
sensitive?: {
api?: boolean;
logs?: boolean;
};
methods?: {
validate?: MethodState;
register?: MethodState;
login?: MethodState;
logout?: MethodState;
refresh?: MethodState;
check?: MethodState;
};
};
//all use events
export type AuthServerUseEvents = {
getUserByMail: {
data: User<"email">;
return: {
user: User<"id" | "email" | "username" | "hashedPassword"> | null;
};
};
getUserByName: {
data: User<"username">;
return: {
user: User<"id" | "email" | "username" | "hashedPassword"> | null;
};
};
hashPassword: {
data: User<"password">;
return: User<"hashedPassword">;
};
checkToken: {
data: User<"refreshToken">;
return: { exists: boolean };
};
storeToken: {
data: User<"refreshToken">;
return: {};
};
deleteToken: {
data: User<"refreshToken">;
return: {};
};
validateMail: {
data: User<"email">;
return: { isValid: boolean };
};
validatePassword: {
data: User<"password">;
return: { isValid: boolean };
};
genId: {
data: User<"email" | "username">;
return: { id: string };
};
storeUser: {
data: { user: User<"id" | "email" | "username" | "hashedPassword"> };
return: {};
};
checkPassword: {
data: User<"password" | "hashedPassword">;
return: { matches: boolean };
};
};
//all intercept events
export type AuthServerInterceptEvents = {
register: {
data: { user: User<"id" | "email" | "username" | "hashedPassword"> };
};
login: {
data: User<"accessToken" | "refreshToken"> & {
user: User<"id" | "email" | "username" | "hashedPassword">;
payload: User<"id">;
};
};
logout: {
data: User<"refreshToken"> & { payload: User<"id"> };
};
refresh: {
data: User<"refreshToken"> & { payload: User<"id"> };
};
check: {
data: User<"accessToken" | "refreshToken"> & {
payload: User<"id">;
};
};
};
export type AuthServerMethod<MethodName extends keyof AuthProtocol> = (
data: AuthRequest<MethodName>
) => Promise<AuthResponse<MethodName>>;
type AuthServerMethods = {
[MethodName in keyof AuthProtocol]?: AuthServerMethod<MethodName>;
};
export class AuthServer extends AuthBase<
AuthServerConfig,
AuthServerUseEvents,
AuthServerInterceptEvents
> {
//all methods
public methods: AuthServerMethods;
//auth server constructor
constructor(config: AuthServerConfig, log: LogFunction) {
//config with default values
const defaultedConfig: DeepRequired<AuthServerConfig> = {
secrets: {
accessToken: config.secrets.accessToken,
refreshToken: config.secrets.refreshToken,
},
expiresIn: config.expiresIn ?? 900, //by default accessToken expires in 900s (15min)
sensitive: {
api: config.sensitive?.api ?? true, //by default api will not directly expose type of error which could leak information of the database
logs: config.sensitive?.logs ?? false, //by default logs WILL directly expose type of error which could leak information of the database
},
methods: {
validate: config.methods?.validate ?? "active", //by default validate is active
register: config.methods?.register ?? "active", //by default register is active
login: config.methods?.login ?? "active", //by default login is active
logout: config.methods?.logout ?? "active", //by default logout is active
refresh: config.methods?.refresh ?? "active", //by default refresh is active
check: config.methods?.check ?? "active", //by default check is active
},
};
//defaults for use event callbacks
const defaultedUseEvents: UseEventCallbacks<AuthServerUseEvents> = {
getUserByMail: undefinedUseEvent("getUserByMail", { user: null }, log),
getUserByName: undefinedUseEvent("getUserByName", { user: null }, log),
storeUser: undefinedUseEvent("storeUser", {}, log),
checkToken: undefinedUseEvent("checkToken", { exists: false }, log),
storeToken: undefinedUseEvent("storeToken", {}, log),
deleteToken: undefinedUseEvent("deleteToken", {}, log),
validateMail: undefinedUseEvent("validateMail", { isValid: false }, log),
validatePassword: undefinedUseEvent(
"validatePassword",
{ isValid: false },
log
),
hashPassword: undefinedUseEvent(
"hashPassword",
{ hashedPassword: "" },
log
),
genId: undefinedUseEvent("genId", { id: "" }, log),
checkPassword: undefinedUseEvent(
"checkPassword",
{ matches: false },
log
),
};
//defaults for intercept event callbacks
const defaultedInterceptEvents: InterceptEventCallbacks<AuthServerInterceptEvents> =
{
register: undefinedInterceptEvent<"register">(),
login: undefinedInterceptEvent<"login">(),
logout: undefinedInterceptEvent<"logout">(),
refresh: undefinedInterceptEvent<"refresh">(),
check: undefinedInterceptEvent<"check">(),
};
//init authbase class
super(defaultedConfig, log, defaultedUseEvents, defaultedInterceptEvents);
//all auth methods
this.methods = {
validate:
this._internal.config.methods.validate !== "removed"
? createValidate(this._internal)
: undefined,
register:
this._internal.config.methods.register !== "removed"
? createRegister(this._internal)
: undefined,
login:
this._internal.config.methods.login !== "removed"
? createLogin(this._internal)
: undefined,
logout:
this._internal.config.methods.logout !== "removed"
? createLogout(this._internal)
: undefined,
refresh:
this._internal.config.methods.refresh !== "removed"
? createRefresh(this._internal)
: undefined,
check:
this._internal.config.methods.check !== "removed"
? createCheck(this._internal)
: undefined,
};
}
}

View File

@ -0,0 +1,28 @@
import { InterceptEventCallbacks, UseEventCallbacks } from "@auth-tools/base";
import { AuthServerInterceptEvents, AuthServerUseEvents } from "./auth";
import { LogFunction } from "@auth-tools/logger";
//for an undefined use event
export function undefinedUseEvent<
Event extends keyof AuthServerUseEvents,
Return extends AuthServerUseEvents[Event]["return"]
>(
event: Event,
returnData: Return,
log: LogFunction
): UseEventCallbacks<AuthServerUseEvents>[Event] {
return (() => {
//complain about unset use event callback
log("error", `The use "${event}" event is not defined!`);
return { ...returnData, serverError: true };
}) as UseEventCallbacks<AuthServerUseEvents>[Event];
}
//for an undefined intercept event
export function undefinedInterceptEvent<
Event extends keyof AuthServerInterceptEvents
>(): InterceptEventCallbacks<AuthServerInterceptEvents>[Event] {
return () => {
return { serverError: false, intercepted: false, interceptCode: 0 };
};
}

View File

@ -0,0 +1,34 @@
import { AuthInternal, UseEventCallbacks, User } from "@auth-tools/base";
import {
AuthServerConfig,
AuthServerInterceptEvents,
AuthServerUseEvents,
} from "./auth";
export default async function (
login: User<"login">["login"],
internal: AuthInternal<
AuthServerConfig,
AuthServerUseEvents,
AuthServerInterceptEvents
>
): Promise<
| ReturnType<UseEventCallbacks<AuthServerUseEvents>["getUserByMail"]>
| ReturnType<UseEventCallbacks<AuthServerUseEvents>["getUserByName"]>
> {
//get user by email with value of login
const getUserByMail = await internal.useEventCallbacks.getUserByMail({
email: login,
});
if (getUserByMail.serverError) return { serverError: true, user: null };
//get user by name with value of login
const getUserByName = await internal.useEventCallbacks.getUserByName({
username: login,
});
if (getUserByName.serverError) return { serverError: true, user: null };
return { serverError: false, user: getUserByMail.user || getUserByName.user };
}

View File

@ -0,0 +1,3 @@
export { AuthServer as default } from "./auth";
export { AuthServer, AuthServerConfig, AuthServerMethod } from "./auth";
export { TokenPayload } from "./tokenUtils";

View File

@ -0,0 +1,31 @@
import { AuthInternal } from "@auth-tools/base";
import {
AuthServerConfig,
AuthServerInterceptEvents,
AuthServerMethod,
AuthServerUseEvents,
} from "../auth";
import { authError, authServerError } from "../senders";
//create _method method
export function create_Method(
internal: AuthInternal<
AuthServerConfig,
AuthServerUseEvents,
AuthServerInterceptEvents
>
): AuthServerMethod<"_method"> {
return async ({}) => {
//_method method is disabled
if (internal.config.methods._method === "disabled") {
internal.log("debug", "The _method method is disabled.");
return authError<"_method", 1>(1, "The _method method is disabled.");
}
try {
} catch (error) {
internal.log("warn", String(error));
return authServerError();
}
};
}

View File

@ -0,0 +1,98 @@
import { AuthInternal } from "@auth-tools/base";
import {
AuthServerConfig,
AuthServerInterceptEvents,
AuthServerMethod,
AuthServerUseEvents,
} from "../auth";
import { authError, authServerError } from "../senders";
import { decodeToken } from "../tokenUtils";
//create check method
export function createCheck(
internal: AuthInternal<
AuthServerConfig,
AuthServerUseEvents,
AuthServerInterceptEvents
>
): AuthServerMethod<"check"> {
return async ({ accessToken, refreshToken }) => {
//check method is disabled
if (internal.config.methods.check === "disabled") {
internal.log("debug", "The check method is disabled.");
return authError<"check", 1>(1, "The check method is disabled.");
}
try {
if (!accessToken || !refreshToken) {
internal.log(
"debug",
'The "accessToken" or "refreshToken" is missing.'
);
return authError<"check", 2>(
2,
'The "accessToken" or "refreshToken" is missing.'
);
}
const decodeRefreshToken = decodeToken(
refreshToken,
internal.config.secrets.refreshToken
);
if (!decodeRefreshToken.valid || !decodeRefreshToken.payload) {
internal.log("debug", 'The "refreshToken" is invalid.');
return authError<"check", 3>(3, 'The "refreshToken" is invalid.');
}
const checkToken = await internal.useEventCallbacks.checkToken({
refreshToken,
});
if (checkToken.serverError) return authServerError();
if (!checkToken.exists) {
internal.log("debug", 'The "refreshToken" does not exist.');
return authError<"check", 4>(4, 'The "refreshToken" does not exist.');
}
const decodeAccessToken = decodeToken(
refreshToken,
internal.config.secrets.accessToken
);
if (!decodeAccessToken.valid || !decodeAccessToken.payload) {
internal.log("debug", 'The "accessToken" is invalid.');
return authError<"check", 5>(5, 'The "accessToken" is invalid.');
}
const intercept = await internal.interceptEventCallbacks.check({
accessToken,
refreshToken,
payload: { id: decodeAccessToken.payload.id },
});
if (intercept.serverError) return authServerError();
if (intercept.intercepted)
return authError<"check", 9>(
9,
"The check request was intercepted.",
intercept.interceptCode
);
return {
auth: {
error: false,
errorType: "method",
message: "Check successful.",
codes: { status: 0, intercept: 0 },
},
data: null,
};
} catch (error) {
internal.log("warn", String(error));
return authServerError();
}
};
}

View File

@ -0,0 +1,129 @@
import { AuthInternal } from "@auth-tools/base";
import {
AuthServerConfig,
AuthServerInterceptEvents,
AuthServerMethod,
AuthServerUseEvents,
} from "../auth";
import { authError, authServerError } from "../senders";
import getUserByLogin from "../getUserByLogin";
import { TokenPayload, generateToken } from "../tokenUtils";
//create login method
export function createLogin(
internal: AuthInternal<
AuthServerConfig,
AuthServerUseEvents,
AuthServerInterceptEvents
>
): AuthServerMethod<"login"> {
return async ({ login, password }) => {
//login method is disabled
if (internal.config.methods.login === "disabled") {
internal.log("debug", "The login method is disabled.");
return authError<"login", 1>(1, "The login method is disabled.");
}
try {
if (!login || !password) {
internal.log(
"debug",
'The "login" ("email" or "username") or "password" is missing.'
);
return authError<"login", 2>(
2,
'The "login" ("email" or "username") or "password" is missing.'
);
}
const getUserByLoginLogin = await getUserByLogin(login, internal);
if (getUserByLoginLogin.serverError) return authServerError();
if (!getUserByLoginLogin.user) {
if (internal.config.sensitive.logs)
internal.log(
"debug",
'The user was not found or the "password" is incorrect.'
);
else internal.log("debug", "The user was not found.");
if (internal.config.sensitive.api)
return authError<"login", 5>(
5,
'The user was not found or the "password" is incorrect.'
);
else return authError<"login", 3>(3, "The user was not found.");
}
const checkPassword = await internal.useEventCallbacks.checkPassword({
password: password,
hashedPassword: getUserByLoginLogin.user.hashedPassword,
});
if (checkPassword.serverError) return authServerError();
if (!checkPassword.matches) {
if (internal.config.sensitive.logs)
internal.log(
"debug",
'The user was not found or the "password" is incorrect.'
);
else internal.log("debug", 'The "password" is incorrect.');
if (internal.config.sensitive.api)
return authError<"login", 5>(
5,
'The user was not found or the "password" is incorrect.'
);
else return authError<"login", 4>(4, 'The "password" is incorrect.');
}
const payload: TokenPayload = { id: getUserByLoginLogin.user.id };
const refreshToken = generateToken(
payload,
internal.config.secrets.refreshToken
);
const accessToken = generateToken(
payload,
internal.config.secrets.accessToken,
internal.config.expiresIn
);
const intercept = await internal.interceptEventCallbacks.login({
user: getUserByLoginLogin.user,
accessToken,
refreshToken,
payload,
});
if (intercept.serverError) return authServerError();
if (intercept.intercepted)
return authError<"login", 9>(
9,
"The login request was intercepted.",
intercept.interceptCode
);
const storeToken = await internal.useEventCallbacks.storeToken({
refreshToken,
});
if (storeToken.serverError) return authServerError();
return {
auth: {
error: false,
errorType: "method",
message: "Login successful.",
codes: { status: 0, intercept: 0 },
},
data: { accessToken, refreshToken },
};
} catch (error) {
internal.log("warn", String(error));
return authServerError();
}
};
}

View File

@ -0,0 +1,87 @@
import { AuthInternal } from "@auth-tools/base";
import {
AuthServerConfig,
AuthServerInterceptEvents,
AuthServerMethod,
AuthServerUseEvents,
} from "../auth";
import { authError, authServerError } from "../senders";
import { decodeToken } from "../tokenUtils";
//create logout method
export function createLogout(
internal: AuthInternal<
AuthServerConfig,
AuthServerUseEvents,
AuthServerInterceptEvents
>
): AuthServerMethod<"logout"> {
return async ({ refreshToken }) => {
//logout method is disabled
if (internal.config.methods.logout === "disabled") {
internal.log("debug", "The logout method is disabled.");
return authError<"logout", 1>(1, "The logout method is disabled.");
}
try {
if (!refreshToken) {
internal.log("debug", 'The "refreshToken" is missing.');
return authError<"logout", 2>(2, 'The "refreshToken" is missing.');
}
const decodeRefreshToken = decodeToken(
refreshToken,
internal.config.secrets.refreshToken
);
if (!decodeRefreshToken.valid || !decodeRefreshToken.payload) {
internal.log("debug", 'The "refreshToken" is invalid.');
return authError<"logout", 3>(3, 'The "refreshToken" is invalid.');
}
const checkToken = await internal.useEventCallbacks.checkToken({
refreshToken,
});
if (checkToken.serverError) return authServerError();
if (!checkToken.exists) {
internal.log("debug", 'The "refreshToken" does not exist.');
return authError<"logout", 4>(4, 'The "refreshToken" does not exist.');
}
const intercept = await internal.interceptEventCallbacks.logout({
refreshToken,
payload: { id: decodeRefreshToken.payload.id },
});
if (intercept.serverError) return authServerError();
if (intercept.intercepted)
return authError<"logout", 9>(
9,
"The logout request was intercepted.",
intercept.interceptCode
);
const deleteToken = await internal.useEventCallbacks.deleteToken({
refreshToken,
});
if (deleteToken.serverError) return authServerError();
return {
auth: {
error: false,
errorType: "method",
message: "Logout successful.",
codes: { status: 0, intercept: 0 },
},
data: null,
};
} catch (error) {
internal.log("warn", String(error));
return authServerError();
}
};
}

View File

@ -0,0 +1,87 @@
import { AuthInternal } from "@auth-tools/base";
import {
AuthServerConfig,
AuthServerInterceptEvents,
AuthServerMethod,
AuthServerUseEvents,
} from "../auth";
import { authError, authServerError } from "../senders";
import { decodeToken, generateToken } from "../tokenUtils";
//create refresh method
export function createRefresh(
internal: AuthInternal<
AuthServerConfig,
AuthServerUseEvents,
AuthServerInterceptEvents
>
): AuthServerMethod<"refresh"> {
return async ({ refreshToken }) => {
//refresh method is disabled
if (internal.config.methods.refresh === "disabled") {
internal.log("debug", "The refresh method is disabled.");
return authError<"refresh", 1>(1, "The refresh method is disabled.");
}
try {
if (!refreshToken) {
internal.log("debug", 'The "refreshToken" is missing.');
return authError<"refresh", 2>(2, 'The "refreshToken" is missing.');
}
const decodeRefreshToken = decodeToken(
refreshToken,
internal.config.secrets.refreshToken
);
if (!decodeRefreshToken.valid || !decodeRefreshToken.payload) {
internal.log("debug", 'The "refreshToken" is invalid.');
return authError<"refresh", 3>(3, 'The "refreshToken" is invalid.');
}
const checkToken = await internal.useEventCallbacks.checkToken({
refreshToken,
});
if (checkToken.serverError) return authServerError();
if (!checkToken.exists) {
internal.log("debug", 'The "refreshToken" does not exist.');
return authError<"refresh", 4>(4, 'The "refreshToken" does not exist.');
}
const intercept = await internal.interceptEventCallbacks.refresh({
refreshToken,
payload: { id: decodeRefreshToken.payload.id },
});
if (intercept.serverError) return authServerError();
if (intercept.intercepted)
return authError<"refresh", 9>(
9,
"The refresh request was intercepted.",
intercept.interceptCode
);
const accessToken = generateToken(
{ id: decodeRefreshToken.payload.id },
internal.config.secrets.accessToken,
internal.config.expiresIn
);
return {
auth: {
error: false,
errorType: "method",
message: "Refresh successful.",
codes: { status: 0, intercept: 0 },
},
data: { accessToken },
};
} catch (error) {
internal.log("warn", String(error));
return authServerError();
}
};
}

View File

@ -0,0 +1,146 @@
import { AuthInternal, User } from "@auth-tools/base";
import {
AuthServerConfig,
AuthServerInterceptEvents,
AuthServerMethod,
AuthServerUseEvents,
} from "../auth";
import { authError, authServerError } from "../senders";
import getUserByLogin from "../getUserByLogin";
//create register method
export function createRegister(
internal: AuthInternal<
AuthServerConfig,
AuthServerUseEvents,
AuthServerInterceptEvents
>
): AuthServerMethod<"register"> {
return async ({ email, username, password }) => {
//register method is disabled
if (internal.config.methods.register === "disabled") {
internal.log("debug", "The registration method is disabled.");
return authError<"register", 1>(
1,
"The registration method is disabled."
);
}
try {
if (!email || !username || !password) {
internal.log(
"debug",
'The "email", "username" or "password" is missing.'
);
return authError<"register", 2>(
2,
'The "email", "username" or "password" is missing.'
);
}
const validateMail = await internal.useEventCallbacks.validateMail({
email,
});
if (validateMail.serverError) return authServerError();
if (!validateMail.isValid) {
internal.log("debug", 'The "email" is malformed.');
return authError<"register", 3>(3, 'The "email" is malformed.');
}
const validatePassword =
await internal.useEventCallbacks.validatePassword({
password,
});
if (validatePassword.serverError) return authServerError();
if (!validatePassword.isValid) {
internal.log("debug", 'The "password" is too weak.');
return authError<"register", 4>(4, 'The "password" is too weak.');
}
const getUserByLoginEmail = await getUserByLogin(email, internal);
if (getUserByLoginEmail.serverError) return authServerError();
if (getUserByLoginEmail.user) {
if (internal.config.sensitive.logs)
internal.log("debug", 'The "login" is already in use.');
else internal.log("debug", 'The "email" is already in use.');
if (internal.config.sensitive.api)
return authError<"register", 7>(7, 'The "login" is already in use.');
else
return authError<"register", 5>(5, 'The "email" is already in use.');
}
const getUserByLoginName = await getUserByLogin(username, internal);
if (getUserByLoginName.serverError) return authServerError();
if (getUserByLoginName.user) {
if (internal.config.sensitive.logs)
internal.log("debug", 'The "login" is already in use.');
else internal.log("debug", 'The "username" is already in use.');
if (internal.config.sensitive.api)
return authError<"register", 7>(7, 'The "login" is already in use.');
else
return authError<"register", 6>(
6,
'The "username" is already in use.'
);
}
const hashPassword = await internal.useEventCallbacks.hashPassword({
password,
});
if (hashPassword.serverError) return authServerError();
const genId = await internal.useEventCallbacks.genId({
email,
username,
});
if (genId.serverError) return authServerError();
const user: User<"id" | "email" | "username" | "hashedPassword"> = {
id: genId.id,
email,
username,
hashedPassword: hashPassword.hashedPassword,
};
const intercept = await internal.interceptEventCallbacks.register({
user,
});
if (intercept.serverError) return authServerError();
if (intercept.intercepted)
return authError<"register", 9>(
9,
"The registration request was intercepted.",
intercept.interceptCode
);
const storeUser = await internal.useEventCallbacks.storeUser({ user });
if (storeUser.serverError) return authServerError();
return {
auth: {
error: false,
errorType: "method",
message: "Registration successful.",
codes: { status: 0, intercept: 0 },
},
data: { id: user.id, email: user.email, username: user.username },
};
} catch (error) {
internal.log("warn", String(error));
return authServerError();
}
};
}

View File

@ -0,0 +1,56 @@
import { AuthInternal } from "@auth-tools/base";
import {
AuthServerConfig,
AuthServerInterceptEvents,
AuthServerMethod,
AuthServerUseEvents,
} from "../auth";
import { authError, authServerError } from "../senders";
import { decodeToken } from "../tokenUtils";
//create validate method
export function createValidate(
internal: AuthInternal<
AuthServerConfig,
AuthServerUseEvents,
AuthServerInterceptEvents
>
): AuthServerMethod<"validate"> {
return async ({ accessToken }) => {
//validate method is disabled
if (internal.config.methods.validate === "disabled") {
internal.log("debug", "The validate method is disabled.");
return authError<"validate", 1>(1, "The validation method is disabled.");
}
try {
if (!accessToken) {
internal.log("debug", 'The "accessToken" is missing.');
return authError<"validate", 2>(2, 'The "accessToken" is missing.');
}
const decodeAccessToken = decodeToken(
accessToken,
internal.config.secrets.accessToken
);
if (!decodeAccessToken.valid || !decodeAccessToken.payload) {
internal.log("debug", 'The "accessToken" is invalid.');
return authError<"validate", 3>(3, 'The "accessToken" is invalid.');
}
return {
auth: {
error: false,
errorType: "method",
message: "Validation successful.",
codes: { status: 0, intercept: 0 },
},
data: { id: decodeAccessToken.payload.id },
};
} catch (error) {
internal.log("warn", String(error));
return authServerError();
}
};
}

View File

@ -0,0 +1,35 @@
import { AuthMessages, AuthProtocol, AuthResponse } from "@auth-tools/base";
export function authError<
Method extends keyof AuthProtocol,
StatusCode extends number
>(
statusCode: StatusCode,
message: AuthMessages[`${Method}_${StatusCode}`],
interceptCode: number = 0
): AuthResponse<Method> {
return {
auth: {
error: true,
errorType: "method",
message: message,
codes: {
status: statusCode,
intercept: interceptCode,
},
},
data: null,
} as AuthResponse<Method>;
}
export function authServerError(): AuthProtocol[keyof AuthProtocol]["responses"]["server_5"] {
return {
auth: {
error: true,
errorType: "server",
message: "An error occurred on the server. Please try again later.",
codes: { status: 5, intercept: 0 },
},
data: null,
};
}

View File

@ -0,0 +1,36 @@
import { User } from "@auth-tools/base";
import { sign, verify } from "jsonwebtoken";
//payload of the token based on User type
//id only, because username and email could change
export type TokenPayload = User<"id">;
//generare an access- or refreshToken
export function generateToken(
payload: TokenPayload,
secret: string,
expiresIn?: number
) {
//gemerate (sign) a token with the payload from secret
return sign(
payload,
secret,
//add expiresIn flag when given (only used for accessTokens)
expiresIn ? { expiresIn: expiresIn } : undefined
);
}
//decode the payload of a token (also verify it is not modified)
export function decodeToken(
token: string,
secret: string
): { valid: boolean; payload: TokenPayload | null } {
try {
//decrypt the token with secret
const data = verify(token, secret) as TokenPayload;
return { valid: true, payload: { id: data.id } };
} catch {
//return unvalid, when token decryption failed
return { valid: false, payload: null };
}
}

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2016",
"lib": ["ES2016"],
"module": "CommonJS",
"moduleResolution": "Node",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}

View File

@ -9,7 +9,7 @@
"private": false,
"scripts": {
"build": "npm run build:remove && tsc",
"build:remove": "rimraf build",
"build:remove": "rimraf dist",
"local": "npm run build && ts-node-dev --respawn local/index.ts",
"prepublish": "npm run build",
"remove": "npm run build:remove && rimraf node_modules yarn.lock package-lock.json pnpm-lock.yaml"