lucia-auth-adapter-surrealdb
Version:
SurrealDB adapter for Lucia
199 lines (198 loc) • 7.32 kB
JavaScript
import { LuciaError } from "lucia-auth";
import Surreal from "surrealdb.js";
const connect = async (args) => {
const opts = args.opts || {};
if (!("uri" in args) ||
!("ns" in opts) ||
!("db" in opts) ||
!("user" in opts) ||
!("pass" in opts)) {
throw "surreal:connection-args:required";
}
const surreal = new Surreal(args.uri, { ...args.opts });
await surreal.wait();
return surreal;
};
const thing = (target, id) => `${target}:${id}`;
const handleId = (target) => `string::replace(id, '${target}:', '') AS id`;
const adapter = (args) => {
const { user: userTarget = "user", session: sessionTarget = "session", key: keyTarget = "key", } = args.opts?.targets || {};
let surreal;
const ensureClient = async () => {
if (surreal)
return;
if ("surreal" in args) {
if (args.surreal.status === 1) {
throw "surreal:connection:closed";
}
surreal = args.surreal;
return;
}
surreal = await connect(args);
};
const query = async (sql, vars) => {
await ensureClient();
const [response] = await surreal.query(sql, vars);
return response && response.status !== "OK" ? [] : response.result;
};
const getThing = async (target, id) => {
await ensureClient();
const [_thing] = await query(`SELECT *, ${handleId(target)} FROM type::table('${target}') WHERE id = $id`, {
id: thing(target, id),
});
return _thing || null;
};
const deleteThing = async (target, id) => {
await ensureClient();
await surreal.delete(thing(target, id));
};
const translateKeyId = (key) => {
if (!key) {
return null;
}
const { key_id, ...rest } = key;
rest.id = key_id;
return rest;
};
const getKey = async (keyId) => {
await ensureClient();
const [key] = await query(`SELECT *, ${handleId(keyTarget)} FROM ${keyTarget} WHERE key_id = $key_id`, {
key_id: keyId,
});
return translateKeyId(key);
};
const createKey = async (key) => {
try {
if (await getKey(key.id)) {
throw new LuciaError("AUTH_DUPLICATE_KEY_ID");
}
const values = {
...key,
id: undefined,
key_id: key.id,
user: `type::thing(${userTarget}, ${key.user_id})`,
};
const [created] = await surreal.create(keyTarget, values);
return translateKeyId(created);
}
catch (error) {
if (error instanceof Error && error?.message?.includes("already")) {
throw new LuciaError("AUTH_DUPLICATE_KEY_ID");
}
throw error;
}
};
return {
async getUser(userId) {
return getThing(userTarget, userId);
},
getSessionAndUserBySessionId: async (sessionId) => {
const session = await getThing(sessionTarget, sessionId);
if (!session) {
return null;
}
const user = await getThing(userTarget, session.user_id);
if (!user) {
return null;
}
return {
user,
session,
};
},
getSession: async (sessionId) => {
return getThing(sessionTarget, sessionId);
},
getSessionsByUserId: async (userId) => {
return query(`SELECT *, ${handleId(sessionTarget)} FROM ${sessionTarget} WHERE user_id = $user_id`, { user_id: userId });
},
setUser: async (userId, userAttributes, key) => {
await ensureClient();
if (key && (await getKey(key.id))) {
throw new LuciaError("AUTH_DUPLICATE_KEY_ID");
}
const [user] = await surreal.create(thing(userTarget, userId), {
...userAttributes,
id: userId,
});
user.id = user?.id ? user?.id.replace(`${userTarget}:`, "") : user?.id;
if (key) {
await createKey(key);
}
return user;
},
async deleteUser(userId) {
await deleteThing(userTarget, userId);
},
setSession: async (session) => {
await ensureClient();
const userDoc = await getThing(userTarget, session.user_id);
if (!userDoc)
throw new LuciaError("AUTH_INVALID_USER_ID");
try {
await surreal.create(sessionTarget, {
...session,
user: `type::thing(${userTarget}, ${session.user_id})`,
});
}
catch (error) {
if (error instanceof Error && error?.message?.includes("already")) {
throw new LuciaError("AUTH_DUPLICATE_SESSION_ID");
}
throw error;
}
},
deleteSession: async (sessionId) => {
await deleteThing(sessionTarget, sessionId);
},
deleteSessionsByUserId: async (userId) => {
await ensureClient();
await surreal.query(`DELETE FROM type::table($tb) WHERE user_id = $user_id`, {
tb: sessionTarget,
user_id: userId,
});
},
updateUserAttributes: async (userId, attributes) => {
await ensureClient();
const user = await getThing(userTarget, userId);
if (!user)
throw new LuciaError("AUTH_INVALID_USER_ID");
await surreal.merge(thing(userTarget, userId), attributes);
return (await getThing(userTarget, userId)) || void 0;
},
getKey: async (keyId) => {
return getKey(keyId);
},
setKey: async (key) => {
const user = await getThing(userTarget, key.user_id);
if (!user)
throw new LuciaError("AUTH_INVALID_USER_ID");
await createKey(key);
},
getKeysByUserId: async (userId) => {
return query(`SELECT *, ${handleId(keyTarget)} FROM ${keyTarget} WHERE user_id = $user_id`, { user_id: userId }).then((keys) => keys.map((k) => translateKeyId(k)));
},
updateKeyPassword: async (key, hashedPassword) => {
if (!(await getKey(key))) {
throw new LuciaError("AUTH_INVALID_KEY_ID");
}
await surreal.query(`UPDATE ${keyTarget} SET hashed_password = '${hashedPassword}' WHERE key_id = $key_id`, {
key_id: key,
});
return (await getKey(key)) || void 0;
},
deleteKeysByUserId: async (userId) => {
ensureClient();
await surreal.query(`DELETE FROM ${keyTarget} WHERE user_id = $user_id`, {
user_id: userId,
});
},
deleteNonPrimaryKey: async (keyId) => {
ensureClient();
await surreal.query(`DELETE FROM ${keyTarget} WHERE key_id = $key_id AND primary_key = false`, {
key_id: keyId,
});
},
};
};
export default adapter;