Skip to content
Source
Sign inSign up

EnvironmentCredentials.test.ts

203 lines7.8 KB

1import * as NodeCryptoLayer from "@effect/platform-node/NodeCrypto";
2import { describe, expect, it } from "@effect/vitest";
3import { PgDialect } from "drizzle-orm/pg-core";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6​
7import * as RelayDb from "../db.ts";
8import { relayEnvironmentCredentials } from "../persistence/schema.ts";
9import * as EnvironmentCredentials from "./EnvironmentCredentials.ts";
10​
11describe("EnvironmentCredentials", () => {
12 it.effect("does not retain credential tokens when lookup persistence fails", () => {
13 const cause = new Error("database unavailable");
14 const token = "t3env_sensitive-credential-token";
15 const whereConditions: Array<unknown> = [];
16 const fakeDb = {
17 select: () => ({
18 from: (table: unknown) => {
19 expect(table).toBe(relayEnvironmentCredentials);
20 return {
21 where: (condition: unknown) => ({
22 limit: () => {
23 whereConditions.push(condition);
24 return Effect.fail(cause);
25 },
26 }),
27 };
28 },
29 }),
30 } as unknown as RelayDb.RelayDb["Service"];
31​
32 return Effect.gen(function* () {
33 const credentials = yield* EnvironmentCredentials.EnvironmentCredentials;
34 const error = yield* Effect.flip(credentials.authenticate(token));
35​
36 expect(error).toMatchObject({
37 _tag: "EnvironmentCredentialAuthenticatePersistenceError",
38 stage: "lookup-credential",
39 });
40 expect(error.cause).toBe(cause);
41 expect(error).not.toHaveProperty("token");
42 expect(whereConditions).toHaveLength(1);
43​
44 const query = new PgDialect().sqlToQuery(whereConditions[0] as never);
45 expect(query.sql).toContain("exists");
46 expect(query.sql).toContain('"relay_environment_links"."environment_id"');
47 expect(query.sql).toContain('"relay_environment_links"."environment_public_key"');
48 expect(query.sql).toContain('"relay_environment_links"."revoked_at" is null');
49 }).pipe(
50 Effect.provide(
51 EnvironmentCredentials.layer.pipe(
52 Layer.provide(NodeCryptoLayer.layer),
53 Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
54 ),
55 ),
56 );
57 });
58​
59 it.effect(
60 "creates opaque credentials and revokes only older credentials for the same key",
61 () => {
62 const insertedValues: Array<{
63 readonly credentialId: string;
64 readonly environmentId: string;
65 readonly environmentPublicKey: string;
66 readonly credentialHash: string;
67 readonly revokedAt: null;
68 readonly createdAt: string;
69 readonly updatedAt: string;
70 }> = [];
71 const staleCredentialRevocations: Array<{
72 readonly values: Record<string, unknown>;
73 readonly condition: unknown;
74 }> = [];
75​
76 const fakeDb = {
77 insert: (table: unknown) => {
78 expect(table).toBe(relayEnvironmentCredentials);
79 return {
80 values: (values: (typeof insertedValues)[number]) => {
81 insertedValues.push(values);
82 return Effect.void;
83 },
84 };
85 },
86 update: (table: unknown) => {
87 expect(table).toBe(relayEnvironmentCredentials);
88 return {
89 set: (values: Record<string, unknown>) => ({
90 where: (condition: unknown) => {
91 staleCredentialRevocations.push({ values, condition });
92 return Effect.void;
93 },
94 }),
95 };
96 },
97 } as unknown as RelayDb.RelayDb["Service"];
98​
99 return Effect.gen(function* () {
100 const credentials = yield* EnvironmentCredentials.EnvironmentCredentials;
101 const token = yield* credentials.create({
102 environmentId: "env_test",
103 environmentPublicKey: "environment-public-key",
104 });
105 const [, credentialId, secret] = token.split("_");
106​
107 expect(token).toMatch(/^t3env_[0-9a-f]{64}_[0-9a-f]{96}$/);
108 expect(credentialId).toHaveLength(64);
109 expect(secret).toHaveLength(96);
110 expect(insertedValues).toHaveLength(1);
111 expect(insertedValues[0]).toMatchObject({
112 credentialId,
113 environmentId: "env_test",
114 environmentPublicKey: "environment-public-key",
115 revokedAt: null,
116 });
117 expect(insertedValues[0]?.credentialHash).toMatch(/^[A-Za-z0-9_-]{43}$/);
118 expect(insertedValues[0]?.credentialHash).not.toContain(token);
119 expect(insertedValues[0]?.createdAt).toBe(insertedValues[0]?.updatedAt);
120 expect(staleCredentialRevocations).toHaveLength(1);
121 expect(staleCredentialRevocations[0]?.values.revokedAt).toEqual(
122 staleCredentialRevocations[0]?.values.updatedAt,
123 );
124​
125 const query = new PgDialect().sqlToQuery(staleCredentialRevocations[0]?.condition as never);
126 expect(query.sql).toContain('"relay_environment_credentials"."environment_id" = $1');
127 expect(query.sql).toContain(
128 '"relay_environment_credentials"."environment_public_key" = $2',
129 );
130 expect(query.sql).toContain('"relay_environment_credentials"."credential_id" <> $3');
131 expect(query.sql).toContain('"relay_environment_credentials"."revoked_at" is null');
132 expect(query.params).toEqual(["env_test", "environment-public-key", credentialId]);
133 }).pipe(
134 Effect.provide(
135 EnvironmentCredentials.layer.pipe(
136 Layer.provide(NodeCryptoLayer.layer),
137 Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
138 ),
139 ),
140 );
141 },
142 );
143​
144 it.effect("revokes active credentials for an environment public key", () => {
145 const updateValues: Array<Record<string, unknown>> = [];
146 const whereConditions: Array<unknown> = [];
147 const fakeDb = {
148 update: (table: unknown) => {
149 expect(table).toBe(relayEnvironmentCredentials);
150 return {
151 set: (values: Record<string, unknown>) => {
152 updateValues.push(values);
153 return {
154 where: (condition: unknown) => {
155 whereConditions.push(condition);
156 return {
157 returning: () => {
158 return Effect.succeed([{ credentialId: "credential-1" }]);
159 },
160 };
161 },
162 };
163 },
164 };
165 },
166 } as unknown as RelayDb.RelayDb["Service"];
167​
168 return Effect.gen(function* () {
169 const credentials = yield* EnvironmentCredentials.EnvironmentCredentials;
170 const revoked = yield* credentials.revokeForEnvironmentPublicKey({
171 environmentId: "env_test",
172 environmentPublicKey: "environment-public-key",
173 });
174​
175 expect(revoked).toBe(true);
176 expect(updateValues).toHaveLength(1);
177 expect(updateValues[0]?.revokedAt).toEqual(updateValues[0]?.updatedAt);
178 expect(whereConditions).toHaveLength(1);
179​
180 const query = new PgDialect().sqlToQuery(whereConditions[0] as never);
181 expect(query.sql).toContain('"relay_environment_credentials"."environment_id" = $1');
182 expect(query.sql).toContain('"relay_environment_credentials"."environment_public_key" = $2');
183 expect(query.sql).toContain('"relay_environment_credentials"."revoked_at" is null');
184 expect(query.sql).toContain("not exists");
185 expect(query.sql).toContain('"relay_environment_links"."environment_id" = $3');
186 expect(query.sql).toContain('"relay_environment_links"."environment_public_key" = $4');
187 expect(query.sql).toContain('"relay_environment_links"."revoked_at" is null');
188 expect(query.params).toEqual([
189 "env_test",
190 "environment-public-key",
191 "env_test",
192 "environment-public-key",
193 ]);
194 }).pipe(
195 Effect.provide(
196 EnvironmentCredentials.layer.pipe(
197 Layer.provide(NodeCryptoLayer.layer),
198 Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
199 ),
200 ),
201 );
202 });
203});