-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathauth.ts
124 lines (103 loc) · 3.44 KB
/
auth.ts
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
113
114
115
116
117
118
119
120
121
122
123
124
import * as YAML from "@std/yaml";
import * as z from "@/common/myZod.ts";
import { config } from "@/common/config.ts";
import { logger } from "@/common/logging.ts";
const AuthFileSchema = z.object({
api_key: z.string(),
admin_key: z.string(),
});
type AuthFile = z.infer<typeof AuthFileSchema>;
export enum AuthKeyPermission {
API = "API",
Admin = "Admin",
}
export class AuthKeys {
public apiKey: string;
public adminKey: string;
public constructor(
apiKey: string,
adminKey: string,
) {
this.apiKey = apiKey;
this.adminKey = adminKey;
}
public verifyKey(testKey: string, permission: AuthKeyPermission): boolean {
switch (permission) {
case AuthKeyPermission.Admin:
return testKey === this.adminKey;
case AuthKeyPermission.API:
return testKey === this.apiKey || testKey === this.adminKey;
default:
return false;
}
}
}
function generateApiToken() {
const buffer = new Uint8Array(16);
crypto.getRandomValues(buffer);
// To hex string
const token = Array.from(buffer)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
return token;
}
export let authKeys: AuthKeys | undefined = undefined;
export async function loadAuthKeys() {
const authFilePath = "api_tokens.yml";
if (config.network.disable_auth) {
logger.warn(
"Disabling authentication makes your instance vulnerable. \n" +
"Set the `disable_auth` flag to false in config.yml " +
"to share this instance with others.",
);
}
const fileInfo = await Deno.stat(authFilePath).catch(() => null);
if (fileInfo?.isFile) {
const rawKeys = await Deno.readTextFile(authFilePath);
const parsedKeys = AuthFileSchema.parse(YAML.parse(rawKeys));
authKeys = new AuthKeys(
parsedKeys.api_key,
parsedKeys.admin_key,
);
} else {
const newAuthFile = AuthFileSchema.parse({
api_key: generateApiToken(),
admin_key: generateApiToken(),
});
authKeys = new AuthKeys(
newAuthFile.api_key,
newAuthFile.admin_key,
);
await Deno.writeFile(
authFilePath,
new TextEncoder().encode(YAML.stringify(newAuthFile)),
);
}
logger.info(
"\n" +
`Your API key is: ${authKeys.apiKey}\n` +
`Your Admin key is: ${authKeys.adminKey}\n\n` +
"If these keys get compromised, make sure to delete api_tokens.yml " +
"and restart the server. Have fun!",
);
}
export function getAuthPermission(headers: Record<string, string>) {
if (config.network.disable_auth) {
return AuthKeyPermission.Admin;
}
let testKey = headers["x-admin-key"] ?? headers["x-api-key"] ??
headers["authorization"];
if (!testKey) {
throw new Error("The provided authentication key is missing.");
}
if (testKey.toLowerCase().startsWith("bearer")) {
testKey = testKey.split(" ")[1];
}
if (authKeys?.verifyKey(testKey, AuthKeyPermission.Admin)) {
return AuthKeyPermission.Admin.toLowerCase();
} else if (authKeys?.verifyKey(testKey, AuthKeyPermission.API)) {
return AuthKeyPermission.API.toLowerCase();
} else {
throw new Error("The provided authentication key is invalid.");
}
}