import { ApolloClient, InMemoryCache } from "@apollo/client"; import { Attribute, Change, Client } from "ldapts"; import { setInterval } from "timers/promises"; import { gql } from "./__generated__"; import sharp from "sharp"; import { BerWriter } from "asn1"; type LLDAPAuthResponse = { token: string; refreshToken: string; }; type LLDAPRefreshResponse = { token: string; }; let ldapClient: Client | null = null; async function getLdapClient() { if (ldapClient === null) { 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 ldapClient.bind(full, process.env.LDAP_BIND_PASSWORD); } return ldapClient; } let authResponse: LLDAPAuthResponse | null = null; async function regenAuthToken() { if (authResponse !== null) { const url = `http://${process.env.LDAP_HOST}:17170/auth/refresh`; const req = await fetch(url, { headers: { "Refresh-Token": authResponse.refreshToken } }); const res: LLDAPRefreshResponse = await req.json(); 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 }) }); authResponse = await req.json(); } // valid for one day, so refresh every 12 hours setTimeout(regenAuthToken, 12 * 60 * 60 * 1000); } async function getAuthToken() { if (authResponse === null) await regenAuthToken(); return authResponse!.token; } let graphQLClient: ApolloClient | null = null; let graphQLCache = new InMemoryCache(); let graphQLAuthToken: string | null = null; async function getGraphQLClient() { if (authResponse === null) { await getAuthToken(); graphQLAuthToken = authResponse!.token; } // We keep track of the auth token we used in the client, so we can // recreate it when it expires/refreshes if (graphQLClient === null || graphQLAuthToken !== authResponse!.token) { graphQLClient = new ApolloClient({ uri: `http://${process.env.LDAP_HOST}:17170/api/graphql`, cache: graphQLCache, headers: { Authorization: `Bearer ${authResponse!.token}` } }); } 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; } async function ensureJpg(avatar: Buffer) { const img = await sharp(avatar).toFormat("jpeg").resize(512, 512); const buf = await img.toBuffer(); return buf.toString("base64"); } 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); }