Skip to content

Commit edb6032

Browse files
committed
Add shared auth cache backend
1 parent 682b6a4 commit edb6032

22 files changed

Lines changed: 1388 additions & 213 deletions

app/admin/signin/page.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@ import {
1010
} from "@/components/ui/card";
1111
import {
1212
DEFAULT_INTERNAL_ADMIN_PATH,
13-
getAuthenticatedUserDestination,
13+
getAuthenticatedSessionDestination,
1414
normalizeSafeCallbackUrl,
1515
} from "@/lib/auth/redirects";
16-
import { getCurrentUser } from "@/lib/auth/server";
17-
import { isInternalAdminUser } from "@/lib/auth/identity";
16+
import { getAuthIdentity } from "@/lib/auth/server";
1817

1918
function extractSearchParam(value: string | string[] | undefined) {
2019
if (Array.isArray(value)) {
@@ -30,14 +29,14 @@ export default async function AdminSignInPage({ searchParams }: Props.Page) {
3029
extractSearchParam(params.callbackUrl) || DEFAULT_INTERNAL_ADMIN_PATH,
3130
DEFAULT_INTERNAL_ADMIN_PATH,
3231
);
33-
const currentUser = await getCurrentUser();
32+
const authIdentity = await getAuthIdentity();
3433

35-
if (currentUser) {
36-
if (!isInternalAdminUser(currentUser)) {
34+
if (authIdentity) {
35+
if (!authIdentity.isInternalAdmin) {
3736
forbidden();
3837
}
3938

40-
redirect(getAuthenticatedUserDestination(currentUser, callbackUrl));
39+
redirect(getAuthenticatedSessionDestination(authIdentity, callbackUrl));
4140
}
4241

4342
return (

app/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import {
99
CardHeader,
1010
CardTitle,
1111
} from "@/components/ui/card";
12-
import { getAuthenticatedUserDestination } from "@/lib/auth/redirects";
13-
import { getCurrentUser } from "@/lib/auth/server";
12+
import { getAuthenticatedSessionDestination } from "@/lib/auth/redirects";
13+
import { getAuthIdentity } from "@/lib/auth/server";
1414

1515
export default async function Page() {
16-
const currentUser = await getCurrentUser();
17-
if (currentUser) {
18-
redirect(getAuthenticatedUserDestination(currentUser));
16+
const authIdentity = await getAuthIdentity();
17+
if (authIdentity) {
18+
redirect(getAuthenticatedSessionDestination(authIdentity));
1919
}
2020

2121
return (

app/signin/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import {
1111
import { resolveAuthErrorMessage } from "@/lib/auth/error-messages";
1212
import {
1313
DEFAULT_PUBLIC_APP_PATH,
14-
getAuthenticatedUserDestination,
14+
getAuthenticatedSessionDestination,
1515
normalizeSafeCallbackUrl,
1616
} from "@/lib/auth/redirects";
17-
import { getCurrentUser } from "@/lib/auth/server";
17+
import { getAuthIdentity } from "@/lib/auth/server";
1818

1919
function extractSearchParam(value: string | string[] | undefined) {
2020
if (Array.isArray(value)) {
@@ -30,9 +30,9 @@ export default async function SignInPage({ searchParams }: Props.Page) {
3030
extractSearchParam(params.callbackUrl) || DEFAULT_PUBLIC_APP_PATH,
3131
DEFAULT_PUBLIC_APP_PATH,
3232
);
33-
const currentUser = await getCurrentUser();
34-
if (currentUser) {
35-
redirect(getAuthenticatedUserDestination(currentUser, callbackUrl));
33+
const authIdentity = await getAuthIdentity();
34+
if (authIdentity) {
35+
redirect(getAuthenticatedSessionDestination(authIdentity, callbackUrl));
3636
}
3737

3838
const errorCode = extractSearchParam(params.error);

lib/auth/onboarding.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import sql from "@/lib/db";
2+
import { syncCachedAuthUser } from "@/lib/auth/user-cache";
23
import { AuthFlowError } from "@/lib/auth/errors";
34
import { INTERNAL_AUTH_PROVIDER, resolveAppSessionIdentity } from "@/lib/auth/identity";
45
import { logRepositoryOperation } from "@/lib/repository-logging";
@@ -91,7 +92,7 @@ export async function completeSignup(
9192
const invitationCodeHash = hashInvitationCode(input.invitationCode);
9293

9394
try {
94-
return await sql.begin(async (tx) => {
95+
const completedUser = await sql.begin(async (tx) => {
9596
const query = tx as unknown as typeof sql;
9697

9798
const [user] = await query<SignupUserRow[]>`
@@ -220,6 +221,9 @@ export async function completeSignup(
220221

221222
return mapCompletedUser(updatedUser);
222223
});
224+
225+
await syncCachedAuthUser(completedUser.id, completedUser);
226+
return completedUser;
223227
} catch (error) {
224228
if (
225229
typeof error === "object" &&

lib/auth/redirects.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ import {
44
isIncompletePublicUser,
55
isInternalAdminUser,
66
} from "@/lib/auth/identity";
7-
import type { AuthUser } from "@/types/db";
7+
import type { AppSessionIdentity, AuthUser } from "@/types/db";
8+
9+
type SessionIdentityLike = {
10+
isInternalAdmin?: boolean;
11+
isSignupComplete?: boolean;
12+
provider?: string;
13+
sessionIdentity?: AppSessionIdentity;
14+
};
815

916
export const AUTH_REQUEST_PATH_HEADER = "x-bbb-request-path";
1017
export const DEFAULT_PUBLIC_APP_PATH = "/books/search";
@@ -106,3 +113,30 @@ export function getAuthenticatedUserDestination(
106113

107114
return safeCallbackUrl;
108115
}
116+
117+
export function getAuthenticatedSessionDestination(
118+
user: SessionIdentityLike,
119+
callbackUrl = DEFAULT_PUBLIC_APP_PATH,
120+
) {
121+
const safeCallbackUrl = normalizeSafeCallbackUrl(
122+
callbackUrl,
123+
DEFAULT_PUBLIC_APP_PATH,
124+
);
125+
126+
if (user.isInternalAdmin === true || user.sessionIdentity === "INTERNAL_ADMIN") {
127+
return DEFAULT_INTERNAL_ADMIN_PATH;
128+
}
129+
130+
if (
131+
user.sessionIdentity === "PUBLIC_INCOMPLETE" ||
132+
(
133+
user.sessionIdentity === undefined &&
134+
typeof user.provider === "string" &&
135+
user.isSignupComplete === false
136+
)
137+
) {
138+
return createSignupHref(safeCallbackUrl);
139+
}
140+
141+
return safeCallbackUrl;
142+
}

lib/auth/server.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,21 @@ import {
1717
createE2ESession,
1818
getE2ECurrentUser,
1919
} from "@/lib/test-harness/auth";
20+
import {
21+
readCachedAuthUserById,
22+
syncCachedAuthUser,
23+
} from "@/lib/auth/user-cache";
2024
import { findUserById } from "@/lib/auth/users";
21-
import type { AuthUser } from "@/types/db";
25+
import type { AppSessionIdentity, AuthUser } from "@/types/db";
26+
27+
export type AuthSessionIdentity = {
28+
id: string;
29+
isInternalAdmin: boolean;
30+
isSignupComplete: boolean;
31+
nickname: string | null;
32+
provider?: string;
33+
sessionIdentity?: AppSessionIdentity;
34+
};
2235

2336
const readAuthSession = cache(async (): Promise<Session | null> => {
2437
const session = await getAuthSessionSafe();
@@ -41,14 +54,44 @@ async function findCurrentUserFromSession(
4154
return null;
4255
}
4356

44-
return findUserById(session.user.id);
57+
const cachedUser = await readCachedAuthUserById(session.user.id);
58+
if (cachedUser !== undefined) {
59+
return cachedUser;
60+
}
61+
62+
const user = await findUserById(session.user.id);
63+
await syncCachedAuthUser(session.user.id, user);
64+
return user;
4565
}
4666

4767
const readCurrentUser = cache(async (): Promise<AuthUser | null> =>
4868
findCurrentUserFromSession(await readAuthSession()),
4969
);
5070

71+
const readAuthIdentity = cache(async (): Promise<AuthSessionIdentity | null> => {
72+
const session = await readAuthSession();
73+
if (!session?.user?.id) {
74+
return null;
75+
}
76+
77+
return {
78+
id: session.user.id,
79+
isInternalAdmin: session.user.isInternalAdmin === true,
80+
isSignupComplete: session.user.isSignupComplete === true,
81+
nickname: typeof session.user.nickname === "string" ? session.user.nickname : null,
82+
provider:
83+
typeof session.user.provider === "string"
84+
? session.user.provider
85+
: undefined,
86+
sessionIdentity:
87+
typeof session.user.sessionIdentity === "string"
88+
? session.user.sessionIdentity
89+
: undefined,
90+
};
91+
});
92+
5193
export const getAuthSession = readAuthSession;
94+
export const getAuthIdentity = readAuthIdentity;
5295
export const getCurrentUser = readCurrentUser;
5396

5497
type RequireCurrentUserOptions = {

0 commit comments

Comments
 (0)