auth routes

This commit is contained in:
Puyodead1 2023-03-23 10:40:37 -04:00
parent 174d34c376
commit a567ca3f51
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
25 changed files with 25602 additions and 9824 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -147,26 +147,26 @@ function apiRoutes() {
}.merge(obj.requestBody); }.merge(obj.requestBody);
} }
if (route.test?.response) { if (route.responses) {
const status = route.test.response.status || 200; for (const [k, v] of Object.entries(route.responses)) {
let schema = { let schema = {
allOf: [ allOf: [
{ {
$ref: `#/components/schemas/${route.test.response.body}`, $ref: `#/components/schemas/${v.body}`,
}, },
{ {
example: route.test.body, example: v.body,
}, },
], ],
}; };
if (!route.test.body) schema = schema.allOf[0]; if (!v.body) schema = schema.allOf[0];
obj.responses = { obj.responses = {
[status]: { [k]: {
...(route.test.response.body ...(v.body
? { ? {
description: description:
obj?.responses?.[status]?.description || "", obj?.responses?.[k]?.description || "",
content: { content: {
"application/json": { "application/json": {
schema: schema, schema: schema,
@ -178,6 +178,7 @@ function apiRoutes() {
}.merge(obj.responses); }.merge(obj.responses);
delete obj.responses.default; delete obj.responses.default;
} }
}
if (p.includes(":")) { if (p.includes(":")) {
obj.parameters = p.match(/:\w+/g)?.map((x) => ({ obj.parameters = p.match(/:\w+/g)?.map((x) => ({
name: x.replace(":", ""), name: x.replace(":", ""),

View File

@ -30,7 +30,18 @@ const router = Router();
router.post( router.post(
"/", "/",
route({ body: "ForgotPasswordSchema" }), route({
body: "ForgotPasswordSchema",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
500: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { login, captcha_key } = req.body as ForgotPasswordSchema; const { login, captcha_key } = req.body as ForgotPasswordSchema;

View File

@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { route, random } from "@spacebar/api"; import { random, route } from "@spacebar/api";
import { Config, ValidRegistrationToken } from "@spacebar/util"; import { Config, ValidRegistrationToken } from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
@ -25,7 +25,10 @@ export default router;
router.get( router.get(
"/", "/",
route({ right: "OPERATOR" }), route({
right: "OPERATOR",
responses: { 200: { body: "GenerateRegistrationTokensResponse" } },
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const count = req.query.count ? parseInt(req.query.count as string) : 1; const count = req.query.count ? parseInt(req.query.count as string) : 1;
const length = req.query.length const length = req.query.length

View File

@ -16,12 +16,20 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Router, Request, Response } from "express"; import { IPAnalysis, getIpAdress, route } from "@spacebar/api";
import { route } from "@spacebar/api"; import { Request, Response, Router } from "express";
import { getIpAdress, IPAnalysis } from "@spacebar/api";
const router = Router(); const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => { router.get(
"/",
route({
responses: {
200: {
body: "LocationMetadataResponse",
},
},
}),
async (req: Request, res: Response) => {
//TODO //TODO
//Note: It's most likely related to legal. At the moment Discord hasn't finished this too //Note: It's most likely related to legal. At the moment Discord hasn't finished this too
const country_code = (await IPAnalysis(getIpAdress(req))).country_code; const country_code = (await IPAnalysis(getIpAdress(req))).country_code;
@ -30,6 +38,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
country_code: country_code, country_code: country_code,
promotional_email_opt_in: { required: true, pre_checked: false }, promotional_email_opt_in: { required: true, pre_checked: false },
}); });
}); },
);
export default router; export default router;

View File

@ -36,7 +36,17 @@ export default router;
router.post( router.post(
"/", "/",
route({ body: "LoginSchema" }), route({
body: "LoginSchema",
responses: {
200: {
body: "TokenResponse",
},
400: {
body: "APIErrorOrCaptchaResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { login, password, captcha_key, undelete } = const { login, password, captcha_key, undelete } =
req.body as LoginSchema; req.body as LoginSchema;

View File

@ -22,9 +22,19 @@ import { Request, Response, Router } from "express";
const router: Router = Router(); const router: Router = Router();
export default router; export default router;
router.post("/", route({}), async (req: Request, res: Response) => { router.post(
"/",
route({
responses: {
204: {},
},
}),
async (req: Request, res: Response) => {
if (req.body.provider != null || req.body.voip_provider != null) { if (req.body.provider != null || req.body.voip_provider != null) {
console.log(`[LOGOUT]: provider or voip provider not null!`, req.body); console.log(
`[LOGOUT]: provider or voip provider not null!`,
req.body,
);
} else { } else {
delete req.body.provider; delete req.body.provider;
delete req.body.voip_provider; delete req.body.voip_provider;
@ -32,4 +42,5 @@ router.post("/", route({}), async (req: Request, res: Response) => {
console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body); console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
} }
res.status(204).send(); res.status(204).send();
}); },
);

View File

@ -16,16 +16,26 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { BackupCode, generateToken, User, TotpSchema } from "@spacebar/util"; import { BackupCode, TotpSchema, User, generateToken } from "@spacebar/util";
import { verifyToken } from "node-2fa"; import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { verifyToken } from "node-2fa";
const router = Router(); const router = Router();
router.post( router.post(
"/", "/",
route({ body: "TotpSchema" }), route({
body: "TotpSchema",
responses: {
200: {
body: "TokenResponse",
},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
// const { code, ticket, gift_code_sku_id, login_source } = // const { code, ticket, gift_code_sku_id, login_source } =
const { code, ticket } = req.body as TotpSchema; const { code, ticket } = req.body as TotpSchema;

View File

@ -41,7 +41,13 @@ function toArrayBuffer(buf: Buffer) {
router.post( router.post(
"/", "/",
route({ body: "WebAuthnTotpSchema" }), route({
body: "WebAuthnTotpSchema",
responses: {
200: { body: "TokenResponse" },
400: { body: "APIErrorResponse" },
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
if (!WebAuthn.fido2) { if (!WebAuthn.fido2) {
// TODO: I did this for typescript and I can't use ! // TODO: I did this for typescript and I can't use !

View File

@ -42,7 +42,13 @@ const router: Router = Router();
router.post( router.post(
"/", "/",
route({ body: "RegisterSchema" }), route({
body: "RegisterSchema",
responses: {
200: { body: "TokenResponse" },
400: { body: "APIErrorOrCaptchaResponse" },
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const body = req.body as RegisterSchema; const body = req.body as RegisterSchema;
const { register, security, limits } = Config.get(); const { register, security, limits } = Config.get();

View File

@ -31,9 +31,20 @@ import { Request, Response, Router } from "express";
const router = Router(); const router = Router();
// TODO: the response interface also returns settings, but this route doesn't actually return that.
router.post( router.post(
"/", "/",
route({ body: "PasswordResetSchema" }), route({
body: "PasswordResetSchema",
responses: {
200: {
body: "TokenResponse",
},
400: {
body: "APIErrorOrCaptchaResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { password, token } = req.body as PasswordResetSchema; const { password, token } = req.body as PasswordResetSchema;

View File

@ -37,9 +37,20 @@ async function getToken(user: User) {
return { token }; return { token };
} }
// TODO: the response interface also returns settings, but this route doesn't actually return that.
router.post( router.post(
"/", "/",
route({ body: "VerifyEmailSchema" }), route({
body: "VerifyEmailSchema",
responses: {
200: {
body: "TokenResponse",
},
400: {
body: "APIErrorOrCaptchaResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { captcha_key, token } = req.body; const { captcha_key, token } = req.body;

View File

@ -24,7 +24,14 @@ const router = Router();
router.post( router.post(
"/", "/",
route({ right: "RESEND_VERIFICATION_EMAIL" }), route({
right: "RESEND_VERIFICATION_EMAIL",
responses: {
204: {},
400: {},
500: {},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const user = await User.findOneOrFail({ const user = await User.findOneOrFail({
where: { id: req.user_id }, where: { id: req.user_id },

View File

@ -16,15 +16,21 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { FieldErrors, User, BackupCodesChallengeSchema } from "@spacebar/util"; import { BackupCodesChallengeSchema, FieldErrors, User } from "@spacebar/util";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
const router = Router(); const router = Router();
router.post( router.post(
"/", "/",
route({ body: "BackupCodesChallengeSchema" }), route({
body: "BackupCodesChallengeSchema",
responses: {
200: { body: "BackupCodesChallengeResponse" },
400: { body: "APIErrorResponse" },
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { password } = req.body as BackupCodesChallengeSchema; const { password } = req.body as BackupCodesChallengeSchema;

View File

@ -17,21 +17,21 @@
*/ */
import { import {
ajv,
DiscordApiErrors, DiscordApiErrors,
EVENT, EVENT,
FieldErrors, FieldErrors,
SpacebarApiErrors,
getPermission,
getRights,
normalizeBody,
PermissionResolvable, PermissionResolvable,
Permissions, Permissions,
RightResolvable, RightResolvable,
Rights, Rights,
SpacebarApiErrors,
ajv,
getPermission,
getRights,
normalizeBody,
} from "@spacebar/util"; } from "@spacebar/util";
import { NextFunction, Request, Response } from "express";
import { AnyValidateFunction } from "ajv/dist/core"; import { AnyValidateFunction } from "ajv/dist/core";
import { NextFunction, Request, Response } from "express";
declare global { declare global {
// TODO: fix this // TODO: fix this
@ -53,6 +53,11 @@ export interface RouteOptions {
permission?: PermissionResolvable; permission?: PermissionResolvable;
right?: RightResolvable; right?: RightResolvable;
body?: `${string}Schema`; // typescript interface name body?: `${string}Schema`; // typescript interface name
responses?: {
[status: number]: {
body?: `${string}Response`;
};
};
test?: { test?: {
response?: RouteResponse; response?: RouteResponse;
body?: unknown; body?: unknown;

View File

@ -58,6 +58,7 @@ export * from "./PurgeSchema";
export * from "./RegisterSchema"; export * from "./RegisterSchema";
export * from "./RelationshipPostSchema"; export * from "./RelationshipPostSchema";
export * from "./RelationshipPutSchema"; export * from "./RelationshipPutSchema";
export * from "./responses";
export * from "./RoleModifySchema"; export * from "./RoleModifySchema";
export * from "./RolePositionUpdateSchema"; export * from "./RolePositionUpdateSchema";
export * from "./SelectProtocolSchema"; export * from "./SelectProtocolSchema";

View File

@ -0,0 +1,6 @@
import { APIErrorResponse } from "./APIErrorResponse";
import { CaptchaRequiredResponse } from "./CaptchaRequiredResponse";
export type APIErrorOrCaptchaResponse =
| CaptchaRequiredResponse
| APIErrorResponse;

View File

@ -0,0 +1,12 @@
export interface APIErrorResponse {
code: number;
message: string;
errors: {
[key: string]: {
_errors: {
message: string;
code: string;
}[];
};
};
}

View File

@ -0,0 +1,4 @@
export interface BackupCodesChallengeResponse {
nonce: string;
regenerate_nonce: string;
}

View File

@ -0,0 +1,5 @@
export interface CaptchaRequiredResponse {
captcha_key: string;
captcha_sitekey: string;
captcha_service: string;
}

View File

@ -0,0 +1,3 @@
export interface GenerateRegistrationTokensResponse {
tokens: string[];
}

View File

@ -0,0 +1,5 @@
export interface LocationMetadataResponse {
consent_required: boolean;
country_code: string;
promotional_email_opt_in: { required: true; pre_checked: false };
}

View File

@ -0,0 +1,6 @@
import { UserSettings } from "../../entities";
export interface TokenResponse {
token: string;
settings: UserSettings;
}

View File

@ -0,0 +1,7 @@
export * from "./APIErrorOrCaptchaResponse";
export * from "./APIErrorResponse";
export * from "./BackupCodesChallengeResponse";
export * from "./CaptchaRequiredResponse";
export * from "./GenerateRegistrationTokensResponse";
export * from "./LocationMetadataResponse";
export * from "./TokenResponse";