This commit is contained in:
Gleb Koval 2021-07-07 19:47:00 +01:00
parent cf5b1a2ce2
commit 04dba57520
No known key found for this signature in database
GPG Key ID: 120F2F6DA9D995FB
5 changed files with 277 additions and 2 deletions

121
src/lib/api.ts Normal file
View File

@ -0,0 +1,121 @@
import type { Graph, Tracker } from "../routes/api/_types";
import type { Profile } from "./hypixel";
import { requestWithDefaults } from "./util";
const request = requestWithDefaults();
/**
* Login into SBDataTracker backend
* @param params Login parameters (no parameters = refresh)
* @returns Login details
*/
export async function postLogin(params?: {
username: string;
password: string;
}): Promise<{
token: string;
username: string;
expires: number;
}> {
let url = "/api/login";
if (params) {
url += `?username=${encodeURIComponent(
params.username
)}&password=${encodeURIComponent(params.password)}`;
}
let response = await request(url, { method: "POST" });
return response.json();
}
/**
* Logout
* @param token Authorization token
*/
export async function postLogout(token: string) {
await request("/api/logout", {
method: "POST",
headers: { Authorization: token }
});
}
/**
* Get a list of profiles
* @param token Authorization token
* @returns A list of profiles
*/
export async function getProfiles(
token?: string
): Promise<(Tracker & { profile: string })[]> {
let response = await request(
"/api/profiles",
token ? { headers: { Authorization: token } } : {}
);
return response.json();
}
/**
* Get a list of skyblock profiles by player
* @param player Player's UUID
* @returns A list of profiles
*/
export async function getPlayerProfiles(player: string): Promise<Profile[]> {
let response = await request(`/api/profiles/${player}`);
return response.json();
}
/**
* Get a list of graphs
* @param token Authorization token
* @returns A list of profiles
*/
export async function getGraphs(
token?: string
): Promise<(Graph & { graph: string })[]> {
let response = await request(
"/api/graphs",
token ? { headers: { Authorization: token } } : {}
);
return response.json();
}
/**
* Get a player username by UUID
* @param uuid Player's UUID
* @returns Player's username
*/
export async function getUsername(uuid: string): Promise<string> {
let response = await request(`/api/username?uuid=${uuid}`);
return response.json();
}
/**
* Get a player UUID by username
* @param username Player's username
* @returns Player's UUID
*/
export async function getUUID(username: string): Promise<string> {
let response = await request(`/api/uuid?username=${username}`);
return response.json();
}
/**
* Create a profile
* @param token Authorization token
* @param uuid Profile UUID
* @param member Member to assign the profile to
* @returns Tracker information
*/
export async function putProfiles(
token: string | undefined,
uuid: string,
member?: string
): Promise<Tracker & { _key: string }> {
let response = await request(
`/api/profiles?uuid=${uuid}` + (member ? `&member=${member}` : ""),
{
method: "PUT",
...(token ? { headers: { Authorization: token } } : {})
}
);
return response.json();
}

122
src/lib/butil.ts Normal file
View File

@ -0,0 +1,122 @@
import { aql, Database } from "arangojs";
import type { Document } from "arangojs/documents";
import { compare } from "bcrypt";
import type { IncomingMessage, ServerResponse } from "http";
import type { Session, User } from "../routes/api/_types";
import { checkPermissions } from "./permissions";
/**
* Find a session in a database
* @param db Arango database
* @param token Session token
* @returns The session data
*/
export async function findSession(
db: Database,
token: string
): Promise<Document<Session> | null> {
if (token) {
const cursor = await db.query(aql`
FOR session IN sessions
RETURN session
`);
while (cursor.hasNext) {
let doc = await cursor.next();
if (await compare(token, doc.token)) {
return doc;
}
}
}
return null;
}
/**
* Find authorizing user
* @param db Arango database
* @param token Session token
* @returns The user data
*/
export async function findAuthorizer(
db: Database,
token?: string
): Promise<(Document<User> & { _session?: Document<Session> }) | null> {
const session = await findSession(db, token || "");
if (!session?.user) {
const cursor = await db.query(aql`
FOR user IN users
FILTER user.username == "_guest"
LIMIT 1
RETURN user
`);
if (cursor.hasNext) {
return cursor.next();
} else {
return null;
}
} else {
const users = db.collection("users");
try {
let user = await users.document(session?.user);
user._session = session;
return user;
} catch (e) {
throw new Error("Invalid user");
}
}
}
type AuthorizationReturn<T extends true | false = boolean> = T extends true
? [null, T]
: [Document<User> & { _token?: string; _session?: Document<Session> }, T];
/**
* Authorize a user
* @param req Request
* @param res Response
* @param db Arango database
* @param permissions The permissions to check against
* @returns The user and whether to return
*/
export async function authorizeRequest(
req: IncomingMessage,
res: ServerResponse,
db: Database,
permissions: string[]
): Promise<AuthorizationReturn> {
const token = req.headers.authorization;
let user:
| (Document<User> & { _token?: string; _session?: Document<Session> })
| null;
try {
user = await findAuthorizer(db, token);
} catch (e) {
res.writeHead(401);
res.end(
JSON.stringify({
message: "Invalid user"
})
);
return [null, true];
}
if (user === null) {
res.writeHead(500);
res.end(
JSON.stringify({
message: "_guest user not found"
})
);
return [null, true];
}
if (!checkPermissions(permissions, user.permissions)) {
res.writeHead(403);
res.end(
JSON.stringify({
message: "Missing permissions"
})
);
return [null, true];
}
user._token = token;
return [user, false];
}

View File

@ -31,6 +31,14 @@ export async function getAPIKeyInformation(key: string): Promise<
}; };
} }
export type Profile = {
profile_id: string;
members: any;
community_upgrades: any | null;
cute_name: string;
banking: any | null;
game_mode: string | null;
};
/** /**
* Get Skyblock profiles by player * Get Skyblock profiles by player
* @param key API key * @param key API key

24
src/lib/mojang.ts Normal file
View File

@ -0,0 +1,24 @@
import { requestWithDefaults } from "./util";
export const ENDPOINT = "https://api.mojang.com/";
const request = requestWithDefaults(ENDPOINT);
/**
* Get a Minecraft username by UUID
* @param uuid Player's UUID
* @returns A username
*/
export async function getUsernameByUUID(uuid: string): Promise<string> {
let response = await request(`/user/profile/${uuid}`);
return (await response.json()).name;
}
/**
* Get a Minecraft UUID by username
* @param name Player's username
* @returns A UUID
*/
export async function getUUIDByUsername(name: string): Promise<string> {
let response = await request(`/users/profiles/minecraft/${name}`);
return (await response.json()).id;
}

View File

@ -16,7 +16,7 @@ export function getFetch(): typeof fetch {
* @returns A fetch-like function * @returns A fetch-like function
*/ */
export function requestWithDefaults( export function requestWithDefaults(
baseURL: string, baseURL?: string,
defaultInit?: RequestInit defaultInit?: RequestInit
): (endpoint: string, init?: RequestInit) => Promise<Response> { ): (endpoint: string, init?: RequestInit) => Promise<Response> {
return async function ( return async function (
@ -24,7 +24,7 @@ export function requestWithDefaults(
overrideInit?: RequestInit overrideInit?: RequestInit
): Promise<Response> { ): Promise<Response> {
let response = await getFetch()( let response = await getFetch()(
new URL(endpoint, baseURL).href, baseURL ? new URL(endpoint, baseURL).href : endpoint,
defaultInit ? deepMerge(defaultInit, overrideInit) : overrideInit defaultInit ? deepMerge(defaultInit, overrideInit) : overrideInit
); );
if (response.ok) { if (response.ok) {