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 230 additions and 74 deletions

Binary file not shown.

Binary file not shown.

View File

@ -147,36 +147,37 @@ 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,
},
}, },
}, }
} : {}),
: {}), },
}, }.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) => ({

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,20 +16,29 @@
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(
//TODO "/",
//Note: It's most likely related to legal. At the moment Discord hasn't finished this too route({
const country_code = (await IPAnalysis(getIpAdress(req))).country_code; responses: {
res.json({ 200: {
consent_required: false, body: "LocationMetadataResponse",
country_code: country_code, },
promotional_email_opt_in: { required: true, pre_checked: false }, },
}); }),
}); async (req: Request, res: Response) => {
//TODO
//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;
res.json({
consent_required: false,
country_code: country_code,
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,14 +22,25 @@ 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(
if (req.body.provider != null || req.body.voip_provider != null) { "/",
console.log(`[LOGOUT]: provider or voip provider not null!`, req.body); route({
} else { responses: {
delete req.body.provider; 204: {},
delete req.body.voip_provider; },
if (Object.keys(req.body).length != 0) }),
console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body); async (req: Request, res: Response) => {
} if (req.body.provider != null || req.body.voip_provider != null) {
res.status(204).send(); console.log(
}); `[LOGOUT]: provider or voip provider not null!`,
req.body,
);
} else {
delete req.body.provider;
delete req.body.voip_provider;
if (Object.keys(req.body).length != 0)
console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
}
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";