feat: associate device id with username when logging in

This commit is contained in:
2024-09-21 13:16:56 -04:00
parent 209a0b477d
commit c1bf12de4f
8 changed files with 1302 additions and 189 deletions

View File

@@ -27,7 +27,7 @@ describe("requests the /token route", () => {
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({ token: "123", deviceId: "123", username: "test" }),
body: JSON.stringify({ token: "123", deviceId: "123" }),
});
expect(res.json()).resolves.toEqual({ success: true });
@@ -41,50 +41,7 @@ describe("requests the /token route", () => {
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({ token: "123", deviceId: "123", username: "test" }),
});
const row = await db
.select()
.from(deviceTokensTable)
.where(eq(deviceTokensTable.deviceId, "123"))
.get();
expect(row).toEqual({
deviceId: "123",
token: "123",
username: "test",
lastConnectedAt: expect.any(String),
});
// since SQL timestamp doesn't support milliseconds, compare to nearest second
expect(
+DateTime.fromSQL(row!.lastConnectedAt!, { zone: "utc" }).startOf(
"second",
),
).toBeGreaterThanOrEqual(+minimumTimestamp.startOf("second"));
});
it("with username as null, should succeed", async () => {
const res = await app.request("/token", {
method: "POST",
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({ token: "123", deviceId: "123", username: null }),
});
expect(res.json()).resolves.toEqual({ success: true });
expect(res.status).toBe(200);
});
it("with username as null, db should contain entry", async () => {
const minimumTimestamp = DateTime.now();
await app.request("/token", {
method: "POST",
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({ token: "123", deviceId: "123", username: null }),
body: JSON.stringify({ token: "123", deviceId: "123" }),
});
const row = await db
@@ -117,7 +74,7 @@ describe("requests the /token route", () => {
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({ token: "124", deviceId: "123", username: null }),
body: JSON.stringify({ token: "124", deviceId: "123" }),
});
expect(res.json()).resolves.toEqual({ success: true });
@@ -134,7 +91,7 @@ describe("requests the /token route", () => {
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({ token: "124", deviceId: "123", username: null }),
body: JSON.stringify({ token: "124", deviceId: "123" }),
});
const row = await db
@@ -157,23 +114,6 @@ describe("requests the /token route", () => {
).toBeGreaterThanOrEqual(+minimumTimestamp.startOf("second"));
});
it("token already exists in db, should fail", async () => {
await db
.insert(deviceTokensTable)
.values({ deviceId: "123", token: "123" });
const res = await app.request("/token", {
method: "POST",
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({ token: "123", deviceId: "124", username: null }),
});
expect(res.json()).resolves.toEqual({ success: false });
expect(res.status).toBe(412);
});
it("token already exists in db, should not insert new entry", async () => {
await db
.insert(deviceTokensTable)
@@ -183,7 +123,7 @@ describe("requests the /token route", () => {
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({ token: "123", deviceId: "124", username: null }),
body: JSON.stringify({ token: "123", deviceId: "124" }),
});
const row = await db
@@ -195,64 +135,6 @@ describe("requests the /token route", () => {
expect(row).toBeUndefined();
});
it("associating a username with a token, should succeed", async () => {
await db
.insert(deviceTokensTable)
.values({ deviceId: "123", token: "123" });
const res = await app.request("/token", {
method: "POST",
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({
token: "123",
deviceId: "123",
username: "aniplay",
}),
});
expect(res.json()).resolves.toEqual({ success: true });
expect(res.status).toBe(200);
});
it("associating a username with a token should update existing entry", async () => {
const minimumTimestamp = DateTime.now();
await db
.insert(deviceTokensTable)
.values({ deviceId: "123", token: "123" });
await app.request("/token", {
method: "POST",
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({
token: "123",
deviceId: "123",
username: "aniplay",
}),
});
const row = await db
.select()
.from(deviceTokensTable)
.where(eq(deviceTokensTable.deviceId, "123"))
.get();
expect(row).toEqual({
deviceId: "123",
token: "123",
username: "aniplay",
lastConnectedAt: expect.any(String),
});
// since SQL timestamp doesn't support milliseconds, compare to nearest second
expect(
+DateTime.fromSQL(row!.lastConnectedAt!, { zone: "utc" }).startOf(
"second",
),
).toBeGreaterThanOrEqual(+minimumTimestamp.startOf("second"));
});
it("token is invalid, should fail", async () => {
mock.module("src/libs/fcm/verifyFcmToken", () => ({
verifyFcmToken: () => false,
@@ -263,7 +145,7 @@ describe("requests the /token route", () => {
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({ token: "123", deviceId: "124", username: null }),
body: JSON.stringify({ token: "123", deviceId: "124" }),
});
expect(res.json()).resolves.toEqual({ success: false });
@@ -279,7 +161,7 @@ describe("requests the /token route", () => {
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({ token: "123", deviceId: "124", username: null }),
body: JSON.stringify({ token: "123", deviceId: "124" }),
});
const row = await db

View File

@@ -3,7 +3,6 @@ import { env } from "hono/adapter";
import mapKeys from "lodash.mapkeys";
import { Case, changeStringCase } from "~/libs/changeStringCase";
import { TokenAlreadyExistsError } from "~/libs/errors/TokenAlreadyExists";
import type { AdminSdkCredentials } from "~/libs/fcm/getGoogleAuthToken";
import { verifyFcmToken } from "~/libs/fcm/verifyFcmToken";
import { readEnvVariable } from "~/libs/readEnvVariable";
@@ -21,7 +20,6 @@ const app = new OpenAPIHono<Env>();
const SaveTokenRequest = z.object({
token: z.string(),
deviceId: z.string(),
username: z.string().nullable(),
});
const SaveTokenResponse = SuccessResponseSchema();
@@ -70,8 +68,7 @@ const route = createRoute({
});
app.openapi(route, async (c) => {
const { token, deviceId, username } =
await c.req.json<typeof SaveTokenRequest._type>();
const { token, deviceId } = await c.req.json<typeof SaveTokenRequest._type>();
try {
const isValidToken = await verifyFcmToken(
@@ -85,12 +82,8 @@ app.openapi(route, async (c) => {
return c.json(ErrorResponse, 401);
}
await saveToken(env(c, "workerd"), deviceId, token, username);
await saveToken(env(c, "workerd"), deviceId, token);
} catch (error) {
if (error instanceof TokenAlreadyExistsError) {
return c.json(ErrorResponse, 412);
}
console.error(new Error("Failed to save token", { cause: error }));
return c.json(ErrorResponse, 500);
}