mirror of
https://github.com/lucaspalomodevelop/auth-tools.git
synced 2026-03-12 22:07:22 +00:00
Merge pull request #5 from auth-tools/dev/1-import_packages
Dev/1 import packages
This commit is contained in:
commit
6f2a6f63f8
21
packages/base/LICENSE
Normal file
21
packages/base/LICENSE
Normal 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.
|
||||
0
packages/base/local/index.ts
Normal file
0
packages/base/local/index.ts
Normal file
29
packages/base/package.json
Normal file
29
packages/base/package.json
Normal 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
73
packages/base/src/auth.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
50
packages/base/src/events/intercept.ts
Normal file
50
packages/base/src/events/intercept.ts
Normal 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
|
||||
>;
|
||||
};
|
||||
41
packages/base/src/events/use.ts
Normal file
41
packages/base/src/events/use.ts
Normal 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
|
||||
>;
|
||||
};
|
||||
12
packages/base/src/helpers.ts
Normal file
12
packages/base/src/helpers.ts
Normal 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">;
|
||||
13
packages/base/src/index.ts
Normal file
13
packages/base/src/index.ts
Normal 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";
|
||||
169
packages/base/src/protocol/index.ts
Normal file
169
packages/base/src/protocol/index.ts
Normal 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"]];
|
||||
44
packages/base/src/protocol/messages.ts
Normal file
44
packages/base/src/protocol/messages.ts
Normal 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.";
|
||||
};
|
||||
19
packages/base/tsconfig.json
Normal file
19
packages/base/tsconfig.json
Normal 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
21
packages/client/LICENSE
Normal 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.
|
||||
0
packages/client/local/index.ts
Normal file
0
packages/client/local/index.ts
Normal file
28
packages/client/package.json
Normal file
28
packages/client/package.json
Normal 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"
|
||||
]
|
||||
}
|
||||
0
packages/client/src/index.ts
Normal file
0
packages/client/src/index.ts
Normal file
19
packages/client/tsconfig.json
Normal file
19
packages/client/tsconfig.json
Normal 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
21
packages/logger/LICENSE
Normal 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.
|
||||
66
packages/logger/local/index.ts
Normal file
66
packages/logger/local/index.ts
Normal 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);
|
||||
29
packages/logger/package.json
Normal file
29
packages/logger/package.json
Normal 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"
|
||||
]
|
||||
}
|
||||
7
packages/logger/src/index.ts
Normal file
7
packages/logger/src/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Logger } from "./logger";
|
||||
|
||||
//create default logger instance
|
||||
const DefaultLogger = new Logger();
|
||||
|
||||
export * from "./logger";
|
||||
export default DefaultLogger;
|
||||
148
packages/logger/src/logger.ts
Normal file
148
packages/logger/src/logger.ts
Normal 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
|
||||
}
|
||||
}
|
||||
19
packages/logger/tsconfig.json
Normal file
19
packages/logger/tsconfig.json
Normal 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
21
packages/server/LICENSE
Normal 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.
|
||||
72
packages/server/local/index.ts
Normal file
72
packages/server/local/index.ts
Normal 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 };
|
||||
});
|
||||
33
packages/server/package.json
Normal file
33
packages/server/package.json
Normal 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
227
packages/server/src/auth.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
28
packages/server/src/events.ts
Normal file
28
packages/server/src/events.ts
Normal 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 };
|
||||
};
|
||||
}
|
||||
34
packages/server/src/getUserByLogin.ts
Normal file
34
packages/server/src/getUserByLogin.ts
Normal 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 };
|
||||
}
|
||||
3
packages/server/src/index.ts
Normal file
3
packages/server/src/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { AuthServer as default } from "./auth";
|
||||
export { AuthServer, AuthServerConfig, AuthServerMethod } from "./auth";
|
||||
export { TokenPayload } from "./tokenUtils";
|
||||
31
packages/server/src/methods/_template.ts.txt
Normal file
31
packages/server/src/methods/_template.ts.txt
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
98
packages/server/src/methods/check.ts
Normal file
98
packages/server/src/methods/check.ts
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
129
packages/server/src/methods/login.ts
Normal file
129
packages/server/src/methods/login.ts
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
87
packages/server/src/methods/logout.ts
Normal file
87
packages/server/src/methods/logout.ts
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
87
packages/server/src/methods/refresh.ts
Normal file
87
packages/server/src/methods/refresh.ts
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
146
packages/server/src/methods/register.ts
Normal file
146
packages/server/src/methods/register.ts
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
56
packages/server/src/methods/validate.ts
Normal file
56
packages/server/src/methods/validate.ts
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
35
packages/server/src/senders.ts
Normal file
35
packages/server/src/senders.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
36
packages/server/src/tokenUtils.ts
Normal file
36
packages/server/src/tokenUtils.ts
Normal 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 };
|
||||
}
|
||||
}
|
||||
19
packages/server/tsconfig.json
Normal file
19
packages/server/tsconfig.json
Normal 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/**/*"]
|
||||
}
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user