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"; export type LLDAPAuthResponse = { token: string; refreshToken: string; }; type LLDAPRefreshResponse = { token: string; }; export type UserInfo = { username: string; displayName: string; email: string; avatar?: 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 userInfo: UserInfo = { username: mutation.data.user.id, displayName: mutation.data.user.displayName, email: mutation.data.user.email, 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 } } }); }