aboutsummaryrefslogtreecommitdiffstats
path: root/packages/trpc/auth.ts
blob: f5ce88e59683b53d815a1a94c49f7fcb3628ab82 (plain) (blame)
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
import { randomBytes } from "crypto";
import * as bcrypt from "bcryptjs";

import { db } from "@karakeep/db";
import { apiKeys } from "@karakeep/db/schema";
import serverConfig from "@karakeep/shared/config";
import { authFailureLogger } from "@karakeep/shared/logger";

// 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 db
      .insert(apiKeys)
      .values({
        name: name,
        userId: userId,
        keyId: id,
        keyHash: secretHash,
      })
      .returning()
  )[0];

  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 db.query.apiKeys.findFirst({
    where: (k, { eq }) => eq(k.keyId, keyId),
    with: {
      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;
}

export async function hashPassword(password: string) {
  return bcrypt.hash(password, BCRYPT_SALT_ROUNDS);
}

export async function validatePassword(email: string, password: string) {
  if (serverConfig.auth.disablePasswordAuth) {
    throw new Error("Password authentication is currently disabled");
  }
  const user = await db.query.users.findFirst({
    where: (u, { eq }) => eq(u.email, email),
  });

  if (!user) {
    throw new Error("User not found");
  }

  if (!user.password) {
    throw new Error("This user doesn't have a password defined");
  }

  const validation = await bcrypt.compare(password, user.password);
  if (!validation) {
    throw new Error("Wrong password");
  }

  return user;
}

export function logAuthenticationError(
  user: string,
  message: string,
  ip: string | null,
): void {
  authFailureLogger.error(
    `Authentication error. User: "${user}", Message: "${message}", IP-Address: "${ip}"`,
  );
}