257 lines
6 KiB
TypeScript
257 lines
6 KiB
TypeScript
import { ApolloClient, InMemoryCache } from "@apollo/client";
|
|
import { Client } from "ldapts";
|
|
import { gql } from "./__generated__";
|
|
import { BerWriter } from "asn1";
|
|
import { User } from "@prisma/client";
|
|
import { ensureJpg } from "@/image";
|
|
import prisma from "./prisma";
|
|
|
|
export type LLDAPAuthResponse = {
|
|
token: string;
|
|
refreshToken: string;
|
|
};
|
|
|
|
type LLDAPRefreshResponse = {
|
|
token: string;
|
|
};
|
|
|
|
export type UserInfo = {
|
|
username: string;
|
|
displayName: string;
|
|
email: string;
|
|
avatar?: string;
|
|
|
|
discordId?: string;
|
|
githubId?: string;
|
|
};
|
|
|
|
async function getLdapClient() {
|
|
if (global.ldapClient == null) {
|
|
global.ldapClient = new Client({
|
|
url: `ldap://${process.env.LDAP_HOST}:3890`
|
|
});
|
|
|
|
const full = `uid=${process.env.LDAP_BIND_USER},ou=people,${process.env.LDAP_DC}`;
|
|
await global.ldapClient.bind(full, process.env.LDAP_BIND_PASSWORD);
|
|
}
|
|
|
|
return global.ldapClient;
|
|
}
|
|
|
|
async function regenAuthToken() {
|
|
if (global.authResponse != null) {
|
|
const url = `http://${process.env.LDAP_HOST}:17170/auth/refresh`;
|
|
const req = await fetch(url, {
|
|
headers: {
|
|
"Refresh-Token": global.authResponse.refreshToken
|
|
}
|
|
});
|
|
const res: LLDAPRefreshResponse = await req.json();
|
|
global.authResponse.token = res.token;
|
|
} else {
|
|
const url = `http://${process.env.LDAP_HOST}:17170/auth/simple/login`;
|
|
const req = await fetch(url, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: JSON.stringify({
|
|
username: process.env.LDAP_BIND_USER,
|
|
password: process.env.LDAP_BIND_PASSWORD
|
|
})
|
|
});
|
|
|
|
global.authResponse = await req.json();
|
|
}
|
|
|
|
// valid for one day, so refresh every 12 hours
|
|
setTimeout(regenAuthToken, 12 * 60 * 60 * 1000);
|
|
}
|
|
|
|
async function getAuthToken() {
|
|
if (global.authResponse == null) await regenAuthToken();
|
|
return global.authResponse!.token;
|
|
}
|
|
|
|
async function getGraphQLClient() {
|
|
if (global.authResponse == null) {
|
|
await getAuthToken();
|
|
}
|
|
|
|
// Remake the client every time because Apollo caching is fucking stupid
|
|
let graphQLClient = new ApolloClient({
|
|
uri: `http://${process.env.LDAP_HOST}:17170/api/graphql`,
|
|
cache: new InMemoryCache(),
|
|
defaultOptions: {
|
|
watchQuery: {
|
|
fetchPolicy: "no-cache",
|
|
errorPolicy: "ignore"
|
|
},
|
|
query: {
|
|
fetchPolicy: "no-cache",
|
|
errorPolicy: "all"
|
|
}
|
|
},
|
|
headers: {
|
|
Authorization: `Bearer ${global.authResponse!.token}`
|
|
}
|
|
});
|
|
|
|
// whoever designed to cache this shit is FUCKING STUPID
|
|
return graphQLClient;
|
|
}
|
|
|
|
export async function getUsers() {
|
|
const client = await getGraphQLClient();
|
|
const query = await client.query({
|
|
query: gql(`
|
|
query GetUsers {
|
|
users {
|
|
id
|
|
}
|
|
}
|
|
`)
|
|
});
|
|
|
|
return query.data.users;
|
|
}
|
|
|
|
export async function createUser(
|
|
username: string,
|
|
displayName: string,
|
|
email: string,
|
|
avatar?: Buffer
|
|
) {
|
|
const fixedAvatar = avatar != null ? await ensureJpg(avatar) : null;
|
|
|
|
const client = await getGraphQLClient();
|
|
const mutation = await client.mutate({
|
|
mutation: gql(`
|
|
mutation CreateUser($user: CreateUserInput!) {
|
|
createUser(user: $user) {
|
|
id
|
|
}
|
|
}
|
|
`),
|
|
variables: {
|
|
user: {
|
|
id: username,
|
|
displayName,
|
|
email,
|
|
avatar: fixedAvatar
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
export async function setPassword(user: string, password: string) {
|
|
const client = await getLdapClient();
|
|
const dn = `uid=${user},ou=people,${process.env.LDAP_DC}`;
|
|
|
|
// god bless random stackoverflow user
|
|
// https://stackoverflow.com/questions/65745679/how-do-i-pass-parameters-to-the-ldapjs-exop-function
|
|
const CTX_SPECIFIC_CLASS = 0b10 << 6;
|
|
const writer = new BerWriter();
|
|
writer.startSequence();
|
|
writer.writeString(dn, CTX_SPECIFIC_CLASS | 0);
|
|
writer.writeString(password, CTX_SPECIFIC_CLASS | 2);
|
|
writer.endSequence();
|
|
|
|
await client.exop("1.3.6.1.4.1.4203.1.11.1", writer.buffer);
|
|
}
|
|
|
|
export async function validateUser(username: string, password: string) {
|
|
const client = new Client({
|
|
url: `ldap://${process.env.LDAP_HOST}:3890`
|
|
});
|
|
|
|
try {
|
|
const dn = `uid=${username},ou=people,${process.env.LDAP_DC}`;
|
|
await client.bind(dn, password);
|
|
await client.unbind();
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function checkUserExists(username: string) {
|
|
const users = await getUsers();
|
|
return users.find((u) => u.id.toLowerCase() === username.toLowerCase());
|
|
}
|
|
|
|
export async function getUserInfo(user: User) {
|
|
if (user.username === null) return null;
|
|
|
|
const client = await getGraphQLClient();
|
|
const mutation = await client.query({
|
|
query: gql(`
|
|
query GetUser($userId: String!) {
|
|
user(userId: $userId) {
|
|
id
|
|
email
|
|
displayName
|
|
avatar
|
|
}
|
|
}
|
|
`),
|
|
variables: {
|
|
userId: user.username
|
|
}
|
|
});
|
|
|
|
const mutationAvatar = mutation.data.user.avatar;
|
|
|
|
const avatar = mutationAvatar
|
|
? `data:image/jpeg;base64,${mutationAvatar}`
|
|
: undefined;
|
|
|
|
const dbUser = await prisma.user.findFirst({
|
|
where: {
|
|
username: user.username
|
|
},
|
|
include: {
|
|
discordAuth: true,
|
|
githubAuth: true
|
|
}
|
|
});
|
|
|
|
const userInfo: UserInfo = {
|
|
username: mutation.data.user.id,
|
|
displayName: mutation.data.user.displayName,
|
|
email: mutation.data.user.email,
|
|
discordId: dbUser?.discordAuth?.id,
|
|
githubId: dbUser?.githubAuth?.id?.toString(),
|
|
avatar
|
|
};
|
|
|
|
return userInfo;
|
|
}
|
|
|
|
export async function updateUser(
|
|
user: User,
|
|
displayName?: string,
|
|
email?: string,
|
|
avatar?: Buffer
|
|
) {
|
|
if (user.username === null) return;
|
|
|
|
const client = await getGraphQLClient();
|
|
const mutation = await client.mutate({
|
|
mutation: gql(`
|
|
mutation UpdateUser($user: UpdateUserInput!) {
|
|
updateUser(user: $user) {
|
|
ok
|
|
}
|
|
}
|
|
`),
|
|
variables: {
|
|
user: {
|
|
id: user.username,
|
|
displayName,
|
|
email,
|
|
avatar: avatar ? await ensureJpg(avatar) : undefined
|
|
}
|
|
}
|
|
});
|
|
}
|