el authentication
parent
e7da5215c3
commit
2aa2ac18ca
|
@ -9,8 +9,8 @@ import {
|
|||
import { AuthenticationData, AuthGuard, NoAuthGuard } from './auth.guard';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiForbiddenResponse,
|
||||
ApiUnauthorizedResponse,
|
||||
// ApiForbiddenResponse,
|
||||
// ApiUnauthorizedResponse,
|
||||
} from '@nestjs/swagger';
|
||||
|
||||
export const AuthData = createParamDecorator(
|
||||
|
@ -28,12 +28,12 @@ export function Auth() {
|
|||
return applyDecorators(
|
||||
UseGuards(AuthGuard),
|
||||
ApiBearerAuth(),
|
||||
ApiUnauthorizedResponse({
|
||||
description: 'No valid authentication presented',
|
||||
}),
|
||||
ApiForbiddenResponse({
|
||||
description: 'The user does not have access to the resource',
|
||||
}),
|
||||
// ApiUnauthorizedResponse({
|
||||
// description: 'No valid authentication presented',
|
||||
// }),
|
||||
// ApiForbiddenResponse({
|
||||
// description: 'The user does not have access to the resource',
|
||||
// }),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -49,8 +49,8 @@ export function ServerAuth(level: 'member' | 'moderator' | 'administrator') {
|
|||
export function NoAuthentication() {
|
||||
return applyDecorators(
|
||||
UseGuards(NoAuthGuard),
|
||||
ApiForbiddenResponse({
|
||||
description: 'Access to the resource is forbidden',
|
||||
}),
|
||||
// ApiForbiddenResponse({
|
||||
// description: 'Access to the resource is forbidden',
|
||||
// }),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -72,12 +72,6 @@
|
|||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
},
|
||||
"401": {
|
||||
"description": "No valid authentication presented"
|
||||
},
|
||||
"403": {
|
||||
"description": "The user does not have access to the resource"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
|
@ -115,12 +109,6 @@
|
|||
"responses": {
|
||||
"201": {
|
||||
"description": ""
|
||||
},
|
||||
"401": {
|
||||
"description": "No valid authentication presented"
|
||||
},
|
||||
"403": {
|
||||
"description": "The user does not have access to the resource"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
|
@ -158,12 +146,6 @@
|
|||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
},
|
||||
"401": {
|
||||
"description": "No valid authentication presented"
|
||||
},
|
||||
"403": {
|
||||
"description": "The user does not have access to the resource"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
|
@ -182,12 +164,6 @@
|
|||
"summary": "List all servers both AutoMod and the user are in",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"401": {
|
||||
"description": "No valid authentication presented"
|
||||
},
|
||||
"403": {
|
||||
"description": "The user does not have access to the resource"
|
||||
},
|
||||
"default": {
|
||||
"description": "",
|
||||
"content": {
|
||||
|
@ -224,12 +200,6 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"401": {
|
||||
"description": "No valid authentication presented"
|
||||
},
|
||||
"403": {
|
||||
"description": "The user does not have access to the resource"
|
||||
},
|
||||
"default": {
|
||||
"description": "",
|
||||
"content": {
|
||||
|
@ -266,12 +236,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "No valid authentication presented"
|
||||
},
|
||||
"403": {
|
||||
"description": "The user does not have access to the resource"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
|
@ -299,9 +263,6 @@
|
|||
}
|
||||
},
|
||||
"responses": {
|
||||
"403": {
|
||||
"description": "Access to the resource is forbidden"
|
||||
},
|
||||
"default": {
|
||||
"description": "",
|
||||
"content": {
|
||||
|
|
|
@ -8,11 +8,11 @@ export type APIRoutes =
|
|||
| { method: 'post', path: '-/servers/{server}/infractions', parts: 3, params: paths['/servers/{server}/infractions']['post']['requestBody']['content']['application/json'], response: undefined }
|
||||
| { method: 'get', path: `/servers/${string}/infractions/${string}`, parts: 4, params: undefined, response: undefined }
|
||||
| { method: 'get', path: '-/servers/{server}/infractions/{id}', parts: 4, params: undefined, response: undefined }
|
||||
| { method: 'get', path: `/servers`, parts: 1, params: undefined, response: undefined }
|
||||
| { method: 'get', path: `/servers/${string}`, parts: 2, params: undefined, response: undefined }
|
||||
| { method: 'get', path: '-/servers/{server}', parts: 2, params: undefined, response: undefined }
|
||||
| { method: 'get', path: `/servers`, parts: 1, params: undefined, response: paths['/servers']['get']['responses']['default']['content']['application/json'] }
|
||||
| { method: 'get', path: `/servers/${string}`, parts: 2, params: undefined, response: paths['/servers/{server}']['get']['responses']['default']['content']['application/json'] }
|
||||
| { method: 'get', path: '-/servers/{server}', parts: 2, params: undefined, response: paths['/servers/{server}']['get']['responses']['default']['content']['application/json'] }
|
||||
| { method: 'get', path: `/auth`, parts: 1, params: undefined, response: paths['/auth']['get']['responses']['200']['content']['application/json'] }
|
||||
| { method: 'post', path: `/auth/login`, parts: 2, params: paths['/auth/login']['post']['requestBody']['content']['application/json'], response: undefined }
|
||||
| { method: 'post', path: `/auth/login`, parts: 2, params: paths['/auth/login']['post']['requestBody']['content']['application/json'], response: paths['/auth/login']['post']['responses']['default']['content']['application/json'] }
|
||||
| { method: 'get', path: `/test`, parts: 1, params: undefined, response: undefined }
|
||||
| { method: 'post', path: `/test`, parts: 1, params: undefined, response: undefined }
|
||||
| { method: 'patch', path: `/test`, parts: 1, params: undefined, response: undefined }
|
||||
|
|
|
@ -136,10 +136,6 @@ export interface operations {
|
|||
};
|
||||
responses: {
|
||||
200: never;
|
||||
/** @description No valid authentication presented */
|
||||
401: never;
|
||||
/** @description The user does not have access to the resource */
|
||||
403: never;
|
||||
};
|
||||
};
|
||||
/** Create an infraction */
|
||||
|
@ -156,10 +152,6 @@ export interface operations {
|
|||
};
|
||||
responses: {
|
||||
201: never;
|
||||
/** @description No valid authentication presented */
|
||||
401: never;
|
||||
/** @description The user does not have access to the resource */
|
||||
403: never;
|
||||
};
|
||||
};
|
||||
/** Fetch a single infraction */
|
||||
|
@ -172,19 +164,11 @@ export interface operations {
|
|||
};
|
||||
responses: {
|
||||
200: never;
|
||||
/** @description No valid authentication presented */
|
||||
401: never;
|
||||
/** @description The user does not have access to the resource */
|
||||
403: never;
|
||||
};
|
||||
};
|
||||
/** List all servers both AutoMod and the user are in */
|
||||
listServers: {
|
||||
responses: {
|
||||
/** @description No valid authentication presented */
|
||||
401: never;
|
||||
/** @description The user does not have access to the resource */
|
||||
403: never;
|
||||
default: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["GetServersResponse"];
|
||||
|
@ -200,10 +184,6 @@ export interface operations {
|
|||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description No valid authentication presented */
|
||||
401: never;
|
||||
/** @description The user does not have access to the resource */
|
||||
403: never;
|
||||
default: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["ServerDetails"];
|
||||
|
@ -219,10 +199,6 @@ export interface operations {
|
|||
"application/json": components["schemas"]["AuthInfo"];
|
||||
};
|
||||
};
|
||||
/** @description No valid authentication presented */
|
||||
401: never;
|
||||
/** @description The user does not have access to the resource */
|
||||
403: never;
|
||||
};
|
||||
};
|
||||
login: {
|
||||
|
@ -232,8 +208,6 @@ export interface operations {
|
|||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Access to the resource is forbidden */
|
||||
403: never;
|
||||
default: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["LoginResponse"];
|
||||
|
|
|
@ -1,24 +1,214 @@
|
|||
"use client";
|
||||
|
||||
import { useBearStore } from "@/components/state";
|
||||
import { setCredentials } from "@/lib/auth";
|
||||
import useAuth from "@/lib/useAuth";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { API } from "oapilib";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type AuthWsCallback = (
|
||||
result: "approved" | "denied" | "errored",
|
||||
error?: any,
|
||||
) => any;
|
||||
class AuthWs {
|
||||
private ws: WebSocket | null = null;
|
||||
private done = false;
|
||||
private token: string;
|
||||
private cb: AuthWsCallback;
|
||||
|
||||
constructor(token: string, cb: AuthWsCallback) {
|
||||
this.token = token;
|
||||
this.cb = cb;
|
||||
this.connect();
|
||||
}
|
||||
|
||||
private connect() {
|
||||
console.debug("Connecting to WS");
|
||||
if (this.ws) {
|
||||
try {
|
||||
this.ws.close();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
let uri = `${
|
||||
new API().config.baseURL
|
||||
}/auth/ws?auth=${encodeURIComponent(this.token)}`;
|
||||
|
||||
if (uri.startsWith("http://")) uri = uri.replace("http://", "ws://");
|
||||
else if (uri.startsWith("https://"))
|
||||
uri = uri.replace("https://", "wss://");
|
||||
else throw new Error("Invalid URL protocol");
|
||||
|
||||
this.ws = new WebSocket(uri);
|
||||
this.ws.onopen = () => {
|
||||
console.debug("WS connected!");
|
||||
};
|
||||
|
||||
this.ws.onclose = () => {
|
||||
if (this.done) {
|
||||
console.debug("WS closed, not reconnecting");
|
||||
} else {
|
||||
console.debug("WS closed, reconnecting in 5 seconds...");
|
||||
|
||||
setTimeout(() => {
|
||||
this.connect();
|
||||
}, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
console.debug("Received WS message");
|
||||
const message = JSON.parse(event.data);
|
||||
console.debug(message);
|
||||
|
||||
if (message.type == "error") {
|
||||
this.done = true;
|
||||
this.cb("errored", message);
|
||||
} else if (message.action) {
|
||||
this.done = true;
|
||||
if (message.action == "approved") {
|
||||
this.cb("approved");
|
||||
} else {
|
||||
this.cb("denied");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default function Auth() {
|
||||
const state = useBearStore();
|
||||
const router = useRouter();
|
||||
const auth = useAuth({ redirect: false });
|
||||
|
||||
const [pageState, setPageState] = useState<
|
||||
"input" | "loading" | "listening" | "denied" | "error"
|
||||
>("input");
|
||||
const [otp, setOtp] = useState("");
|
||||
const [token, setToken] = useState("");
|
||||
const [ws, setWs] = useState<AuthWs | null>(null);
|
||||
const [error, setError] = useState("");
|
||||
const [uid, setUid] = useState(" ");
|
||||
|
||||
useEffect(() => {
|
||||
if (auth?.authenticated) {
|
||||
router.push("/dashboard");
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Let's get you logged in.</h1>
|
||||
</>
|
||||
);
|
||||
switch (pageState) {
|
||||
case "input":
|
||||
return (
|
||||
<>
|
||||
<h1>Let's get you logged in.</h1>
|
||||
<label htmlFor="uid">Enter your Revolt ID: </label>
|
||||
<input
|
||||
type="text"
|
||||
id="uid"
|
||||
value={uid}
|
||||
onChange={(e) => setUid(e.currentTarget.value)}
|
||||
/>{" "}
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (!uid) return;
|
||||
|
||||
// Let's use a fresh API instance just in case the current one still has authentication set
|
||||
const api = new API();
|
||||
|
||||
setPageState("loading");
|
||||
|
||||
try {
|
||||
const result = await api.post("/auth/login", {
|
||||
user: uid.trim(),
|
||||
});
|
||||
|
||||
setPageState("listening");
|
||||
setOtp(result.otp);
|
||||
setToken(result.token);
|
||||
setWs(
|
||||
new AuthWs(
|
||||
result.token,
|
||||
async (res, error) => {
|
||||
if (res == "errored") {
|
||||
setPageState("error");
|
||||
setError(JSON.stringify(error));
|
||||
}
|
||||
if (res == "approved") {
|
||||
await setCredentials({
|
||||
token: result.token,
|
||||
});
|
||||
state.api = new API({
|
||||
authentication: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${result.token}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
state.auth =
|
||||
await state.api.get(
|
||||
"/auth",
|
||||
);
|
||||
|
||||
router.push("/dashboard");
|
||||
}
|
||||
if (res == "denied") {
|
||||
setPageState("denied");
|
||||
}
|
||||
setOtp("");
|
||||
setToken("");
|
||||
setWs(null);
|
||||
setUid("");
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
try {
|
||||
setError(JSON.stringify(e, null, 4));
|
||||
} catch (e) {
|
||||
setError("" + e);
|
||||
}
|
||||
setPageState("error");
|
||||
}
|
||||
}}
|
||||
>
|
||||
Go!
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
case "loading":
|
||||
return (
|
||||
<>
|
||||
<span>Just one second...</span>
|
||||
</>
|
||||
);
|
||||
case "listening":
|
||||
return (
|
||||
<>
|
||||
<h1>Just one more step</h1>
|
||||
<p>
|
||||
To finish, run the following in AutoMod's direct
|
||||
messages: <code>/login {otp}</code>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
case "error":
|
||||
return (
|
||||
<>
|
||||
<h1>Hm, something went wrong.</h1>
|
||||
<p>{error}</p>
|
||||
</>
|
||||
);
|
||||
case "denied":
|
||||
return (
|
||||
<>
|
||||
<h1>Authentication request was denied.</h1>
|
||||
<button onClick={() => setPageState("input")}>
|
||||
Try again?
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue