API
This commit is contained in:
parent
16cd5b70e3
commit
ac8b5a911e
|
@ -0,0 +1,34 @@
|
|||
export interface Config<TS = string, T = any> {
|
||||
type: TS;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
username: string;
|
||||
password?: string;
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
token: string;
|
||||
exp: number;
|
||||
type: string;
|
||||
user: string;
|
||||
}
|
||||
|
||||
// Configs
|
||||
export type Tracker = Config<
|
||||
"tracker",
|
||||
{
|
||||
name: string;
|
||||
members: string[];
|
||||
}
|
||||
>;
|
||||
|
||||
export type Graph = Config<
|
||||
"graph",
|
||||
{
|
||||
name: string;
|
||||
value: (string | number)[];
|
||||
}
|
||||
>;
|
|
@ -0,0 +1,29 @@
|
|||
import { aql } from "arangojs";
|
||||
import type { Document } from "arangojs/documents";
|
||||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import { authorizeRequest } from "../../lib/butil";
|
||||
import { Permissions } from "../../lib/permissions";
|
||||
import { db } from "../../server";
|
||||
import type { Graph } from "./_types";
|
||||
|
||||
export async function get(req: IncomingMessage, res: ServerResponse) {
|
||||
const [_, exit] = await authorizeRequest(req, res, db, [Permissions.VIEW]);
|
||||
if (exit) return;
|
||||
|
||||
let graphs: (Graph & { graph: string })[] = [];
|
||||
const cursor = await db.query(aql`
|
||||
FOR doc IN config
|
||||
FILTER doc.type == "graph"
|
||||
RETURN doc
|
||||
`);
|
||||
while (cursor.hasNext) {
|
||||
const next: Document<Graph> = await cursor.next();
|
||||
graphs.push({
|
||||
graph: next._key,
|
||||
type: next.type,
|
||||
data: next.data
|
||||
});
|
||||
}
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify(graphs));
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import { parse, serialize } from "cookie";
|
||||
import hat from "hat";
|
||||
import { hash, compare } from "bcrypt";
|
||||
import {
|
||||
accessTokenExpire,
|
||||
db,
|
||||
refreshTokenExpire,
|
||||
saltRounds
|
||||
} from "../../server";
|
||||
import { aql, Database } from "arangojs";
|
||||
import { findSession } from "../../lib/butil";
|
||||
import type { Document } from "arangojs/documents";
|
||||
import type { User } from "./_types";
|
||||
|
||||
export async function post(req: IncomingMessage, res: ServerResponse) {
|
||||
const cookies = parse(req.headers.cookie || "");
|
||||
let user: Document<User>;
|
||||
|
||||
// Check for existing refresh session
|
||||
let session = await findSession(db, cookies.refresh);
|
||||
// If session exists, refresh it
|
||||
if (session !== null && session.exp > Date.now()) {
|
||||
// Check user
|
||||
try {
|
||||
user = await db.collection("users").document(session.user);
|
||||
} catch (e) {
|
||||
res.writeHead(401);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "Non-existent user"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const sessions = db.collection("sessions");
|
||||
await sessions.remove(session, { silent: true });
|
||||
const { token, refreshToken, tokenExp, refreshTokenExp } =
|
||||
await newSession(db, session.user);
|
||||
|
||||
res.writeHead(200, {
|
||||
"Set-Cookie": serialize("refresh", refreshToken, {
|
||||
expires: new Date(refreshTokenExp),
|
||||
httpOnly: true,
|
||||
sameSite: true,
|
||||
secure: true
|
||||
})
|
||||
});
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
token,
|
||||
username: user.username,
|
||||
expires: tokenExp
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for parameters
|
||||
let url = new URL(req.url!, "https://example.com/");
|
||||
if (
|
||||
url.searchParams.get("username") === null ||
|
||||
url.searchParams.get("password") === null
|
||||
) {
|
||||
res.writeHead(400);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message:
|
||||
"Both username and password query parameters should be set"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check user
|
||||
const cursor = await db.query(aql`
|
||||
FOR user IN users
|
||||
FILTER user.username == ${url.searchParams.get("username")}
|
||||
LIMIT 1
|
||||
RETURN user
|
||||
`);
|
||||
if (!cursor.hasNext) {
|
||||
res.writeHead(401);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "Non-existent user"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
user = await cursor.next();
|
||||
|
||||
// Check password
|
||||
if (!user.password) {
|
||||
res.writeHead(401);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "Cannot authenticate with this user"
|
||||
})
|
||||
);
|
||||
return;
|
||||
} else if (
|
||||
!(await compare(url.searchParams.get("password")!, user.password))
|
||||
) {
|
||||
res.writeHead(401);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "Invalid password"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Creat new session and return
|
||||
const { token, refreshToken, tokenExp, refreshTokenExp } = await newSession(
|
||||
db,
|
||||
user._key
|
||||
);
|
||||
res.writeHead(200, {
|
||||
"Set-Cookie": serialize("refresh", refreshToken, {
|
||||
expires: new Date(refreshTokenExp),
|
||||
httpOnly: true,
|
||||
sameSite: true,
|
||||
secure: true
|
||||
})
|
||||
});
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
token,
|
||||
username: user.username,
|
||||
expires: tokenExp
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async function newSession(
|
||||
db: Database,
|
||||
user: string
|
||||
): Promise<{
|
||||
token: string;
|
||||
tokenExp: number;
|
||||
refreshToken: string;
|
||||
refreshTokenExp: number;
|
||||
}> {
|
||||
const sessions = db.collection("sessions");
|
||||
const token = hat();
|
||||
let tokenExp = Date.now() + accessTokenExpire;
|
||||
const refreshToken = hat();
|
||||
let refreshTokenExp = Date.now() + refreshTokenExpire;
|
||||
await sessions.saveAll([
|
||||
{
|
||||
token: await hash(token, saltRounds),
|
||||
exp: tokenExp,
|
||||
type: "access",
|
||||
user: user
|
||||
},
|
||||
{
|
||||
token: await hash(refreshToken, saltRounds),
|
||||
exp: refreshTokenExp,
|
||||
type: "refresh",
|
||||
user: user
|
||||
}
|
||||
]);
|
||||
return {
|
||||
token,
|
||||
tokenExp,
|
||||
refreshToken,
|
||||
refreshTokenExp
|
||||
};
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import { parse, serialize } from "cookie";
|
||||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import { authorizeRequest, findSession } from "../../lib/butil";
|
||||
import { db } from "../../server";
|
||||
|
||||
export async function post(req: IncomingMessage, res: ServerResponse) {
|
||||
const [user, exit] = await authorizeRequest(req, res, db, []);
|
||||
if (exit) return;
|
||||
|
||||
const cookies = parse(req.headers.cookie || "");
|
||||
const refresh = await findSession(db, cookies["refresh"] || "");
|
||||
|
||||
if (!user?._session || !refresh) {
|
||||
res.writeHead(400);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "No session to logout"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const sessions = db.collection("sessions");
|
||||
await sessions.remove(refresh!);
|
||||
await sessions.remove(user!._session!);
|
||||
|
||||
res.writeHead(204, {
|
||||
"Set-Cookie": serialize("refresh", "deleted", {
|
||||
expires: new Date(0),
|
||||
httpOnly: true,
|
||||
sameSite: true,
|
||||
secure: true
|
||||
})
|
||||
});
|
||||
res.end();
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
import { aql } from "arangojs";
|
||||
import type { Document } from "arangojs/documents";
|
||||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import { getProfileByUUID, getProfilesByPlayer } from "../../lib/hypixel";
|
||||
import { Permissions } from "../../lib/permissions";
|
||||
import { authorizeRequest } from "../../lib/butil";
|
||||
import { db, API_KEY } from "../../server";
|
||||
import type { Tracker } from "./_types";
|
||||
|
||||
export async function get(req: IncomingMessage, res: ServerResponse) {
|
||||
const [_, exit] = await authorizeRequest(req, res, db, [Permissions.VIEW]);
|
||||
if (exit) return;
|
||||
|
||||
let profiles: (Tracker & { profile: string })[] = [];
|
||||
const cursor = await db.query(aql`
|
||||
FOR doc IN config
|
||||
FILTER doc.type == "tracker"
|
||||
RETURN doc
|
||||
`);
|
||||
while (cursor.hasNext) {
|
||||
const next: Document<Tracker> = await cursor.next();
|
||||
profiles.push({
|
||||
profile: next._key,
|
||||
type: next.type,
|
||||
data: next.data
|
||||
});
|
||||
}
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify(profiles));
|
||||
}
|
||||
|
||||
export async function put(req: IncomingMessage, res: ServerResponse) {
|
||||
const [_, exit] = await authorizeRequest(req, res, db, [
|
||||
Permissions.ADD_PROFILE
|
||||
]);
|
||||
if (exit) return;
|
||||
|
||||
const url = new URL(req.url!, "https://example.com/");
|
||||
if (!url.searchParams.get("uuid")) {
|
||||
res.writeHead(400);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "uuid query parameter should be set"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const profile = await getProfileByUUID(
|
||||
API_KEY || "",
|
||||
url.searchParams.get("uuid")!
|
||||
);
|
||||
if (!profile.profile) {
|
||||
res.writeHead(profile._response.status);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "Hypixel error"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
const config = db.collection("config");
|
||||
let tracker: Tracker & { _key: string } = {
|
||||
_key: profile.profile!.profile_id,
|
||||
type: "tracker",
|
||||
data: {
|
||||
name: "",
|
||||
members: Object.keys(profile.profile!.members)
|
||||
}
|
||||
};
|
||||
const profiles = await getProfilesByPlayer(
|
||||
API_KEY || "",
|
||||
url.searchParams.get("member") || tracker.data.members[0]
|
||||
);
|
||||
tracker.data.name =
|
||||
profiles.profiles?.find(
|
||||
v => v.profile_id === profile.profile?.profile_id
|
||||
)?.cute_name || "";
|
||||
|
||||
if (url.searchParams.get("member")) {
|
||||
tracker.data.members = [url.searchParams.get("member")!].concat(
|
||||
tracker.data.members.filter(
|
||||
m => m !== url.searchParams.get("member")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await config.save(tracker);
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify(tracker));
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import { getProfilesByPlayer } from "../../../lib/hypixel";
|
||||
import { API_KEY } from "../../../server";
|
||||
|
||||
export async function get(
|
||||
req: IncomingMessage & { params: { player: string } },
|
||||
res: ServerResponse
|
||||
) {
|
||||
let response = await getProfilesByPlayer(API_KEY || "", req.params.player);
|
||||
if (response.success && response.profiles !== undefined) {
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify(response.profiles));
|
||||
} else {
|
||||
res.writeHead(response._response.status);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "Unsuccessful Hypixel API response"
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import { getUsernameByUUID } from "../../lib/mojang";
|
||||
|
||||
export async function get(req: IncomingMessage, res: ServerResponse) {
|
||||
const url = new URL(req.url!, "https://example.com/");
|
||||
if (!url.searchParams.get("uuid")) {
|
||||
res.writeHead(400);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "uuid query parameter should be set"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
let response = await getUsernameByUUID(url.searchParams.get("uuid")!);
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify(response));
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import { getUUIDByUsername } from "../../lib/mojang";
|
||||
|
||||
export async function get(req: IncomingMessage, res: ServerResponse) {
|
||||
const url = new URL(req.url!, "https://example.com/");
|
||||
if (!url.searchParams.get("username")) {
|
||||
res.writeHead(400);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "username query parameter should be set"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
let response = await getUUIDByUsername(url.searchParams.get("username")!);
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify(response));
|
||||
}
|
Loading…
Reference in New Issue