1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
import NextAuth, { NextAuthOptions, getServerSession } from "next-auth";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import AuthentikProvider from "next-auth/providers/authentik";
import serverConfig from "@remember/shared/config";
import { prisma } from "@remember/db";
import { DefaultSession } from "next-auth";
import * as bcrypt from "bcrypt";
import { randomBytes } from "crypto";
declare module "next-auth" {
/**
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
export interface Session {
user: {
id: string;
} & DefaultSession["user"];
}
}
const providers = [];
if (serverConfig.auth.authentik) {
providers.push(AuthentikProvider(serverConfig.auth.authentik));
}
export const authOptions: NextAuthOptions = {
// Configure one or more authentication providers
adapter: PrismaAdapter(prisma),
providers: providers,
callbacks: {
session({ session, user }) {
session.user = { ...user };
return session;
},
},
};
export const authHandler = NextAuth(authOptions);
export const getServerAuthSession = () => getServerSession(authOptions);
// API Keys
const BCRYPT_SALT_ROUNDS = 10;
const API_KEY_PREFIX = "ak1";
export async function generateApiKey(name: string, userId: string) {
const id = randomBytes(10).toString("hex");
const secret = randomBytes(10).toString("hex");
const secretHash = await bcrypt.hash(secret, BCRYPT_SALT_ROUNDS);
const plain = `${API_KEY_PREFIX}_${id}_${secret}`;
const key = await prisma.apiKey.create({
data: {
name: name,
userId: userId,
keyId: id,
keyHash: secretHash,
},
});
return {
id: key.id,
name: key.name,
createdAt: key.createdAt,
key: plain,
};
}
function parseApiKey(plain: string) {
const parts = plain.split("_");
if (parts.length != 3) {
throw new Error(
`Malformd API key. API keys should have 3 segments, found ${parts.length} instead.`,
);
}
if (parts[0] !== API_KEY_PREFIX) {
throw new Error(`Malformd API key. Got unexpected key prefix.`);
}
return {
keyId: parts[1],
keySecret: parts[2],
};
}
export async function authenticateApiKey(key: string) {
const { keyId, keySecret } = parseApiKey(key);
const apiKey = await prisma.apiKey.findUnique({
where: {
keyId,
},
include: {
user: true,
},
});
if (!apiKey) {
throw new Error("API key not found");
}
const hash = apiKey.keyHash;
const validation = await bcrypt.compare(keySecret, hash);
if (!validation) {
throw new Error("Invalid API Key");
}
return apiKey.user;
}
|