Simple Auth.js Adapter I made for SurrealDB. It uses REST API. Surrealdb-js package doesn't have to be installed. It's also important to change the generateSessionToken() function in SolidAuthConfig as shown below (#6074).
import {
Adapter,
AdapterUser,
AdapterAccount,
AdapterSession,
} from "@auth/core/adapters";
export interface SurrealAdapterOptions {
user: string;
password: string;
namespace: string;
database: string;
url: string;
}
async function querySql<T = any>(
options: SurrealAdapterOptions,
sql: string
): Promise<Response<T>> {
const buffer = Buffer.from(options.user + ":" + options.password);
const auth = `Basic ${buffer.toString("base64")}`;
const headers = {
Accept: "application/json",
Authorization: auth,
NS: options.namespace,
DB: options.database,
};
const response = await fetch(options.url, {
method: "POST",
headers,
body: sql,
});
return await response.json();
}
type Response<T> = { time: string; status: string; result: T[] }[];
const id = (id: string) => id.split(":")[1] ?? id;
const userFromResponse = (response: any): AdapterUser => {
return {
...response,
id: id(response.id),
emailVerified: response.emailVerified
? new Date(response.emailVerified)
: null,
};
};
const dataFromUser = (user: Partial<AdapterUser>): any => {
return {
...user,
id: undefined,
emailVerified: user.emailVerified ? user.emailVerified.toISOString() : null,
};
};
const accountFromResponse = (response: any): AdapterAccount => {
return {
...response,
id: id(response.id),
userId: response.userId ? id(response.userId) : null,
};
};
const dataFromAccount = (account: AdapterAccount): any => {
return {
...account,
id: undefined,
userId: `user:${account.userId}`,
};
};
const sessionFromResponse = (response: any): AdapterSession => {
const userId = response.userId;
return {
userId: id(typeof userId === "string" ? userId : userId.id),
expires: new Date(response.expires ?? ""),
sessionToken: response.sessionToken ?? "",
};
};
const dataFromSession = (session: AdapterSession): any => {
return {
...session,
userId: `user:${session.userId}`,
expires: session.expires.toISOString(),
};
};
const SurrealAdapter = (options: SurrealAdapterOptions): Adapter => {
return {
async createUser(user) {
const data = dataFromUser(user);
const query = `INSERT INTO user ${JSON.stringify(data)};`;
const response = await querySql(options, query);
return userFromResponse(response[0].result[0]);
},
async getUser(id) {
const query = `SELECT * FROM user:${id} LIMIT 1`;
const response = await querySql(options, query);
if (response[0].result.length == 1) {
return userFromResponse(response[0].result[0]);
} else {
return null;
}
},
async getUserByEmail(email) {
const query = `SELECT * FROM user WHERE email="${email}" LIMIT 1;`;
const response = await querySql(options, query);
if (response[0].result.length == 1) {
return userFromResponse(response[0].result[0]);
} else {
return null;
}
},
async getUserByAccount({ providerAccountId, provider }) {
const query = `SELECT userId FROM account WHERE providerAccountId="${providerAccountId}" AND provider="${provider}" FETCH userId;`;
const response = await querySql(options, query);
if (response[0].result.length == 1 && response[0].result[0].userId) {
return userFromResponse(response[0].result[0].userId);
} else {
return null;
}
},
async updateUser(user) {
const data = dataFromUser(user);
const query = `INSERT INTO user:${user.id} ${JSON.stringify(data)};`;
const response = await querySql(options, query);
return userFromResponse(response[0].result[0]);
},
async deleteUser(userId) {
const query = `
BEGIN TRANSACTION;
DELETE account WHERE userId=user:${userId};
DELETE session WHERE userId=user:${userId};
DELETE user:${userId};
COMMIT TRANSACTION;
`;
await querySql(options, query);
},
async linkAccount(account) {
const data = dataFromAccount(account);
const query = `INSERT INTO account ${JSON.stringify(data)};`;
const response = await querySql(options, query);
return accountFromResponse(response[0].result[0]);
},
async unlinkAccount({ providerAccountId, provider }) {
const query = `DELETE account WHERE providerAccountId="${providerAccountId}" AND provider="${provider}";`;
await querySql(options, query);
},
async createSession(session) {
const data = dataFromSession(session);
const query = `INSERT INTO session ${JSON.stringify(data)};`;
const response = await querySql(options, query);
return sessionFromResponse(response[0].result[0]);
},
async getSessionAndUser(sessionToken) {
const query = `SELECT * FROM session WHERE sessionToken="${sessionToken}" FETCH userId;`;
const response = await querySql(options, query);
if (response[0].result.length == 1 && response[0].result[0].userId) {
const session = sessionFromResponse(response[0].result[0]);
const user = userFromResponse(response[0].result[0].userId);
return { session, user };
} else {
return null;
}
},
async updateSession(session) {
const expires = session.expires?.toISOString();
const query = `UPDATE session SET expires="${expires}" WHERE sessionToken="${session.sessionToken}";`;
const response = await querySql(options, query);
if (response[0].result.length == 1) {
return sessionFromResponse(response[0].result[0]);
} else {
return null;
}
},
async deleteSession(sessionToken) {
const query = `DELETE session WHERE sessionToken="${sessionToken}";`;
await querySql(options, query);
},
async createVerificationToken({ identifier, expires, token }) {
const data = { identifier, expires, token };
const query = `INSERT INTO vertification_token ${JSON.stringify(data)};`;
await querySql(options, query);
return data;
},
async useVerificationToken({ identifier, token }) {
const query = `DELETE verification_token WHERE identifier="${identifier}" AND token="${token}" LIMIT 1";`;
const response = await querySql(options, query);
if (response[0].result.length == 1) {
const result = response[0].result[0];
return {
...result,
expires: new Date(result.expires),
};
} else {
return null;
}
},
};
};
export default SurrealAdapter;
export const authOptions: SolidAuthConfig = {
adapter: SurrealAdapter({
user: process.env.SURREAL_USER as string,
password: process.env.SURREAL_PASSWORD as string,
url: process.env.SURREAL_SQL_URL as string, // ../sql has to be included
namespace: "test",
database: "test",
}),
session: {
strategy: "database",
maxAge: 30 * 24 * 60 * 60, // 30 days
updateAge: 24 * 60 * 60, // 24 hours
generateSessionToken: () => crypto.randomUUID(),
},
providers: [
// @ts-expect-error Types are wrong
GitHub({
clientId: process.env.AUTH_GITHUB_ID as string,
clientSecret: process.env.AUTH_GITHUB_SECRET as string,
}),
],
};