diff --git a/README.md b/README.md
index 39302ba38..f3916508e 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,7 @@ packages
├─ api
| └─ tRPC v11 router definition
├─ auth
- | └─ Authentication using next-auth. **NOTE: Only for Next.js app, not Expo**
+ | └─ Authentication using next-auth.
├─ db
| └─ Typesafe db calls using Drizzle & Supabase
└─ ui
@@ -130,6 +130,12 @@ To add a new package, simply run `pnpm turbo gen init` in the monorepo root. Thi
The generator sets up the `package.json`, `tsconfig.json` and a `index.ts`, as well as configures all the necessary configurations for tooling around your package such as formatting, linting and typechecking. When the package is created, you're ready to go build out the package.
+### 4. Configuring Next-Auth to work with Expo
+
+In order to get Next-Auth to work with Expo, you must either:
+- Add your local IP (e.g. 192.168.x.y) to your OAuth provider (by default Discord), and you may have to update this if it gets reassigned
+- Deploy the Auth Proxy and point your OAuth provider to the proxy
+
## FAQ
### Does the starter include Solito?
@@ -138,14 +144,6 @@ No. Solito will not be included in this repo. It is a great tool if you want to
Integrating Solito into this repo isn't hard, and there are a few [official templates](https://github.com/nandorojo/solito/tree/master/example-monorepos) by the creators of Solito that you can use as a reference.
-### What auth solution should I use instead of Next-Auth.js for Expo?
-
-I've left this kind of open for you to decide. Some options are [Clerk](https://clerk.dev), [Supabase Auth](https://supabase.com/docs/guides/auth), [Firebase Auth](https://firebase.google.com/docs/auth/) or [Auth0](https://auth0.com/docs). Note that if you're dropping the Expo app for something more "browser-like", you can still use Next-Auth.js for those. [See an example in a Plasmo Chrome Extension here](https://github.com/t3-oss/create-t3-turbo/tree/chrome/apps/chrome).
-
-The Clerk.dev team even made an [official template repository](https://github.com/clerkinc/t3-turbo-and-clerk) integrating Clerk.dev with this repo.
-
-During Launch Week 7, Supabase [announced their fork](https://supabase.com/blog/launch-week-7-community-highlights#t3-turbo-x-supabase) of this repo integrating it with their newly announced auth improvements. You can check it out [here](https://github.com/supabase-community/create-t3-turbo).
-
### Does this pattern leak backend code to my client applications?
No, it does not. The `api` package should only be a production dependency in the Next.js application where it's served. The Expo app, and all other apps you may add in the future, should only add the `api` package as a dev dependency. This lets you have full typesafety in your client applications, while keeping your backend code safe.
diff --git a/apps/expo/package.json b/apps/expo/package.json
index e657e919c..534b6abfd 100644
--- a/apps/expo/package.json
+++ b/apps/expo/package.json
@@ -27,8 +27,10 @@
"expo-dev-client": "~4.0.13",
"expo-linking": "~6.3.1",
"expo-router": "~3.5.11",
+ "expo-secure-store": "^13.0.1",
"expo-splash-screen": "~0.27.4",
"expo-status-bar": "~1.12.1",
+ "expo-web-browser": "^13.0.3",
"nativewind": "~4.0.36",
"react": "18.3.1",
"react-dom": "18.3.1",
diff --git a/apps/expo/src/app/index.tsx b/apps/expo/src/app/index.tsx
index 35795de59..e9f1e2471 100644
--- a/apps/expo/src/app/index.tsx
+++ b/apps/expo/src/app/index.tsx
@@ -1,11 +1,12 @@
import { useState } from "react";
-import { Pressable, Text, TextInput, View } from "react-native";
+import { Button, Pressable, Text, TextInput, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Link, Stack } from "expo-router";
import { FlashList } from "@shopify/flash-list";
import type { RouterOutputs } from "~/utils/api";
import { api } from "~/utils/api";
+import { useSignIn, useSignOut, useUser } from "~/utils/auth";
function PostCard(props: {
post: RouterOutputs["post"]["all"][number];
@@ -94,17 +95,36 @@ function CreatePost() {
);
}
+function MobileAuth() {
+ const user = useUser();
+ const signIn = useSignIn();
+ const signOut = useSignOut();
+
+ return (
+ <>
+
+ {user?.name ?? "Not logged in"}
+
+ (user ? signOut() : signIn())}
+ title={user ? "Sign Out" : "Sign In With Discord"}
+ color={"#5B65E9"}
+ />
+ >
+ );
+}
+
export default function Index() {
const utils = api.useUtils();
const postQuery = api.post.all.useQuery();
const deletePostMutation = api.post.delete.useMutation({
- onSettled: () => utils.post.all.invalidate().then(),
+ onSettled: () => utils.post.all.invalidate(),
});
return (
-
+
{/* Changes page title visible on the header */}
@@ -112,12 +132,7 @@ export default function Index() {
Create T3 Turbo
- void utils.post.all.invalidate()}
- className="flex items-center rounded-lg bg-primary p-2"
- >
- Refresh posts
-
+
diff --git a/apps/expo/src/utils/api.tsx b/apps/expo/src/utils/api.tsx
index 0ff34c7f0..157241a5d 100644
--- a/apps/expo/src/utils/api.tsx
+++ b/apps/expo/src/utils/api.tsx
@@ -1,5 +1,4 @@
import { useState } from "react";
-import Constants from "expo-constants";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { httpBatchLink, loggerLink } from "@trpc/client";
import { createTRPCReact } from "@trpc/react-query";
@@ -7,37 +6,15 @@ import superjson from "superjson";
import type { AppRouter } from "@acme/api";
+import { getBaseUrl } from "./base-url";
+import { getToken } from "./session-store";
+
/**
* A set of typesafe hooks for consuming your API.
*/
export const api = createTRPCReact();
export { type RouterInputs, type RouterOutputs } from "@acme/api";
-/**
- * Extend this function when going to production by
- * setting the baseUrl to your production API URL.
- */
-const getBaseUrl = () => {
- /**
- * Gets the IP address of your host-machine. If it cannot automatically find it,
- * you'll have to manually set it. NOTE: Port 3000 should work for most but confirm
- * you don't have anything else running on it, or you'd have to change it.
- *
- * **NOTE**: This is only for development. In production, you'll want to set the
- * baseUrl to your production API URL.
- */
- const debuggerHost = Constants.expoConfig?.hostUri;
- const localhost = debuggerHost?.split(":")[0];
-
- if (!localhost) {
- // return "https://turbo.t3.gg";
- throw new Error(
- "Failed to get localhost. Please point to your production server.",
- );
- }
- return `http://${localhost}:3000`;
-};
-
/**
* A wrapper for your app that provides the TRPC context.
* Use only in _app.tsx
@@ -59,6 +36,10 @@ export function TRPCProvider(props: { children: React.ReactNode }) {
headers() {
const headers = new Map();
headers.set("x-trpc-source", "expo-react");
+
+ const token = getToken();
+ if (token) headers.set("Authorization", `Bearer ${token}`);
+
return Object.fromEntries(headers);
},
}),
diff --git a/apps/expo/src/utils/auth.tsx b/apps/expo/src/utils/auth.tsx
new file mode 100644
index 000000000..a2f9194f4
--- /dev/null
+++ b/apps/expo/src/utils/auth.tsx
@@ -0,0 +1,53 @@
+import * as Linking from "expo-linking";
+import { useRouter } from "expo-router";
+import * as Browser from "expo-web-browser";
+
+import { api } from "./api";
+import { getBaseUrl } from "./base-url";
+import { deleteToken, setToken } from "./session-store";
+
+export const signIn = async () => {
+ const signInUrl = `${getBaseUrl()}/api/auth/signin`;
+ const redirectTo = Linking.createURL("/login");
+ const result = await Browser.openAuthSessionAsync(
+ `${signInUrl}?expo-redirect=${encodeURIComponent(redirectTo)}`,
+ redirectTo,
+ );
+
+ if (result.type !== "success") return;
+ const url = Linking.parse(result.url);
+ const sessionToken = String(url.queryParams?.session_token);
+ if (!sessionToken) return;
+
+ setToken(sessionToken);
+};
+
+export const useUser = () => {
+ const { data: session } = api.auth.getSession.useQuery();
+ return session?.user ?? null;
+};
+
+export const useSignIn = () => {
+ const utils = api.useUtils();
+ const router = useRouter();
+
+ return async () => {
+ await signIn();
+ await utils.invalidate();
+ router.replace("/");
+ };
+};
+
+export const useSignOut = () => {
+ const utils = api.useUtils();
+ const signOut = api.auth.signOut.useMutation();
+ const router = useRouter();
+
+ return async () => {
+ const res = await signOut.mutateAsync();
+ if (!res.success) return;
+ await deleteToken();
+ await utils.invalidate();
+ router.replace("/");
+ };
+};
diff --git a/apps/expo/src/utils/base-url.tsx b/apps/expo/src/utils/base-url.tsx
new file mode 100644
index 000000000..94dfbbc3f
--- /dev/null
+++ b/apps/expo/src/utils/base-url.tsx
@@ -0,0 +1,26 @@
+import Constants from "expo-constants";
+
+/**
+ * Extend this function when going to production by
+ * setting the baseUrl to your production API URL.
+ */
+export const getBaseUrl = () => {
+ /**
+ * Gets the IP address of your host-machine. If it cannot automatically find it,
+ * you'll have to manually set it. NOTE: Port 3000 should work for most but confirm
+ * you don't have anything else running on it, or you'd have to change it.
+ *
+ * **NOTE**: This is only for development. In production, you'll want to set the
+ * baseUrl to your production API URL.
+ */
+ const debuggerHost = Constants.expoConfig?.hostUri;
+ const localhost = debuggerHost?.split(":")[0];
+
+ if (!localhost) {
+ // return "https://turbo.t3.gg";
+ throw new Error(
+ "Failed to get localhost. Please point to your production server.",
+ );
+ }
+ return `http://${localhost}:3000`;
+};
diff --git a/apps/expo/src/utils/session-store.ts b/apps/expo/src/utils/session-store.ts
new file mode 100644
index 000000000..8a46399ce
--- /dev/null
+++ b/apps/expo/src/utils/session-store.ts
@@ -0,0 +1,6 @@
+import * as SecureStore from "expo-secure-store";
+
+const key = "session_token";
+export const getToken = () => SecureStore.getItem(key);
+export const deleteToken = () => SecureStore.deleteItemAsync(key);
+export const setToken = (v: string) => SecureStore.setItem(key, v);
diff --git a/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts b/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts
index 16c8300c3..22ddf2c4f 100644
--- a/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts
+++ b/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts
@@ -1,3 +1,81 @@
-export { GET, POST } from "@acme/auth";
+import { cookies } from "next/headers";
+import { NextRequest, NextResponse } from "next/server";
+
+import { handlers, isSecureContext } from "@acme/auth";
export const runtime = "edge";
+
+const EXPO_COOKIE_NAME = "__acme-expo-redirect-state";
+const AUTH_COOKIE_PATTERN = /authjs\.session-token=([^;]+)/;
+
+/**
+ * Noop in production.
+ *
+ * In development, rewrite the request URL to use localhost instead of host IP address
+ * so that Expo Auth works without getting trapped by Next.js CSRF protection.
+ * @param req The request to modify
+ * @returns The modified request.
+ */
+function rewriteRequestUrlInDevelopment(req: NextRequest) {
+ if (isSecureContext) return req;
+
+ const host = req.headers.get("host");
+ const newURL = new URL(req.url);
+ newURL.host = host ?? req.nextUrl.host;
+ return new NextRequest(newURL, req);
+}
+
+export const POST = async (_req: NextRequest) => {
+ // First step must be to correct the request URL.
+ const req = rewriteRequestUrlInDevelopment(_req);
+ return handlers.POST(req);
+};
+
+export const GET = async (
+ _req: NextRequest,
+ props: { params: { nextauth: string[] } },
+) => {
+ // First step must be to correct the request URL.
+ const req = rewriteRequestUrlInDevelopment(_req);
+
+ const nextauthAction = props.params.nextauth[0];
+ const isExpoSignIn = req.nextUrl.searchParams.get("expo-redirect");
+ const isExpoCallback = cookies().get(EXPO_COOKIE_NAME);
+
+ if (nextauthAction === "signin" && !!isExpoSignIn) {
+ // set a cookie we can read in the callback
+ // to know to send the user back to expo
+ cookies().set({
+ name: EXPO_COOKIE_NAME,
+ value: isExpoSignIn,
+ maxAge: 60 * 10, // 10 min
+ path: "/",
+ });
+ }
+
+ if (nextauthAction === "callback" && !!isExpoCallback) {
+ cookies().delete(EXPO_COOKIE_NAME);
+
+ // Run original handler, then extract the session token from the response
+ // Send it back via a query param in the Expo deep link. The Expo app
+ // will then get that and set it in the session storage.
+ const authResponse = await handlers.GET(req);
+ const setCookie = authResponse.headers
+ .getSetCookie()
+ .find((cookie) => AUTH_COOKIE_PATTERN.test(cookie));
+ const match = setCookie?.match(AUTH_COOKIE_PATTERN)?.[1];
+
+ if (!match)
+ throw new Error(
+ "Unable to find session cookie: " +
+ JSON.stringify(authResponse.headers.getSetCookie()),
+ );
+
+ const url = new URL(isExpoCallback.value);
+ url.searchParams.set("session_token", match);
+ return NextResponse.redirect(url);
+ }
+
+ // Every other request just calls the default handler
+ return handlers.GET(req);
+};
diff --git a/packages/api/src/router/auth.ts b/packages/api/src/router/auth.ts
index 230c08855..ad53a60ad 100644
--- a/packages/api/src/router/auth.ts
+++ b/packages/api/src/router/auth.ts
@@ -1,5 +1,7 @@
import type { TRPCRouterRecord } from "@trpc/server";
+import { invalidateSessionToken } from "@acme/auth";
+
import { protectedProcedure, publicProcedure } from "../trpc";
export const authRouter = {
@@ -9,4 +11,11 @@ export const authRouter = {
getSecretMessage: protectedProcedure.query(() => {
return "you can see this secret message!";
}),
+ signOut: protectedProcedure.mutation(async (opts) => {
+ if (!opts.ctx.token) {
+ return { success: false };
+ }
+ await invalidateSessionToken(opts.ctx.token);
+ return { success: true };
+ }),
} satisfies TRPCRouterRecord;
diff --git a/packages/api/src/trpc.ts b/packages/api/src/trpc.ts
index af70f5fa6..9d85d4460 100644
--- a/packages/api/src/trpc.ts
+++ b/packages/api/src/trpc.ts
@@ -11,8 +11,20 @@ import superjson from "superjson";
import { ZodError } from "zod";
import type { Session } from "@acme/auth";
+import { auth, validateToken } from "@acme/auth";
import { db } from "@acme/db/client";
+/**
+ * Isomorphic Session getter for API requests
+ * - Expo requests will have a session token in the Authorization header
+ * - Next.js requests will have a session token in cookies
+ */
+const isomorphicGetSession = async (headers: Headers) => {
+ const authToken = headers.get("Authorization") ?? null;
+ if (authToken) return validateToken(authToken);
+ return auth();
+};
+
/**
* 1. CONTEXT
*
@@ -25,18 +37,20 @@ import { db } from "@acme/db/client";
*
* @see https://trpc.io/docs/server/context
*/
-export const createTRPCContext = (opts: {
+export const createTRPCContext = async (opts: {
headers: Headers;
session: Session | null;
}) => {
- const session = opts.session;
- const source = opts.headers.get("x-trpc-source") ?? "unknown";
+ const authToken = opts.headers.get("Authorization") ?? null;
+ const session = await isomorphicGetSession(opts.headers);
+ const source = opts.headers.get("x-trpc-source") ?? "unknown";
console.log(">>> tRPC Request from", source, "by", session?.user);
return {
session,
db,
+ token: authToken,
};
};
diff --git a/packages/auth/env.ts b/packages/auth/env.ts
index 897749ac3..e27e8516b 100644
--- a/packages/auth/env.ts
+++ b/packages/auth/env.ts
@@ -10,6 +10,7 @@ export const env = createEnv({
process.env.NODE_ENV === "production"
? z.string().min(1)
: z.string().min(1).optional(),
+ NODE_ENV: z.enum(["development", "production"]),
},
client: {},
experimental__runtimeEnv: {},
diff --git a/packages/auth/package.json b/packages/auth/package.json
index ccd60be15..733baeb1a 100644
--- a/packages/auth/package.json
+++ b/packages/auth/package.json
@@ -19,6 +19,7 @@
},
"dependencies": {
"@acme/db": "workspace:*",
+ "@auth/core": "0.31.0",
"@auth/drizzle-adapter": "^1.1.0",
"@t3-oss/env-nextjs": "^0.10.1",
"next": "^14.2.3",
diff --git a/packages/auth/src/config.ts b/packages/auth/src/config.ts
index f8c047972..42697258a 100644
--- a/packages/auth/src/config.ts
+++ b/packages/auth/src/config.ts
@@ -1,10 +1,17 @@
-import type { DefaultSession, NextAuthConfig } from "next-auth";
+import type {
+ DefaultSession,
+ NextAuthConfig,
+ Session as NextAuthSession,
+} from "next-auth";
+import { skipCSRFCheck } from "@auth/core";
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import Discord from "next-auth/providers/discord";
import { db } from "@acme/db/client";
import { Account, Session, User } from "@acme/db/schema";
+import { env } from "../env";
+
declare module "next-auth" {
interface Session {
user: {
@@ -13,12 +20,20 @@ declare module "next-auth" {
}
}
+const adapter = DrizzleAdapter(db, {
+ usersTable: User,
+ accountsTable: Account,
+ sessionsTable: Session,
+});
+
+export const isSecureContext = env.NODE_ENV !== "development";
+
export const authConfig = {
- adapter: DrizzleAdapter(db, {
- usersTable: User,
- accountsTable: Account,
- sessionsTable: Session,
- }),
+ adapter,
+ // In development, we need to skip checks to allow Expo to work
+ skipCSRFCheck: isSecureContext ? undefined : skipCSRFCheck,
+ trustHost: !isSecureContext,
+ secret: env.AUTH_SECRET,
providers: [Discord],
callbacks: {
session: (opts) => {
@@ -35,3 +50,22 @@ export const authConfig = {
},
},
} satisfies NextAuthConfig;
+
+export const validateToken = async (
+ token: string,
+): Promise => {
+ const sessionToken = token.slice("Bearer ".length);
+ const session = await adapter.getSessionAndUser?.(sessionToken);
+ return session
+ ? {
+ user: {
+ ...session.user,
+ },
+ expires: session.session.expires.toISOString(),
+ }
+ : null;
+};
+
+export const invalidateSessionToken = async (token: string) => {
+ await adapter.deleteSession?.(token);
+};
diff --git a/packages/auth/src/index.rsc.ts b/packages/auth/src/index.rsc.ts
index 4094ae6df..0373b25cb 100644
--- a/packages/auth/src/index.rsc.ts
+++ b/packages/auth/src/index.rsc.ts
@@ -5,12 +5,7 @@ import { authConfig } from "./config";
export type { Session } from "next-auth";
-const {
- handlers: { GET, POST },
- auth: defaultAuth,
- signIn,
- signOut,
-} = NextAuth(authConfig);
+const { handlers, auth: defaultAuth, signIn, signOut } = NextAuth(authConfig);
/**
* This is the main way to get session data for your RSCs.
@@ -18,4 +13,10 @@ const {
*/
const auth = cache(defaultAuth);
-export { GET, POST, auth, signIn, signOut };
+export { handlers, auth, signIn, signOut };
+
+export {
+ invalidateSessionToken,
+ validateToken,
+ isSecureContext,
+} from "./config";
diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts
index 47df45675..f9f39f624 100644
--- a/packages/auth/src/index.ts
+++ b/packages/auth/src/index.ts
@@ -4,11 +4,12 @@ import { authConfig } from "./config";
export type { Session } from "next-auth";
-const {
- handlers: { GET, POST },
- auth,
- signIn,
- signOut,
-} = NextAuth(authConfig);
-
-export { GET, POST, auth, signIn, signOut };
+const { handlers, auth, signIn, signOut } = NextAuth(authConfig);
+
+export { handlers, auth, signIn, signOut };
+
+export {
+ invalidateSessionToken,
+ validateToken,
+ isSecureContext,
+} from "./config";
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ec82c5fd0..b5157e754 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -99,12 +99,18 @@ importers:
expo-router:
specifier: ~3.5.11
version: 3.5.11(expo-constants@16.0.1(expo@51.0.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)))(expo-linking@6.3.1(expo@51.0.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)))(expo-modules-autolinking@1.11.1)(expo-status-bar@1.12.1)(expo@51.0.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8))(react-native-reanimated@3.10.1(@babel/core@7.24.7)(react-native@0.74.1(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.10.1(react-native@0.74.1(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1))(react@18.3.1))(react-native-screens@3.31.1(react-native@0.74.1(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1))(react@18.3.1))(react-native@0.74.1(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1))(react@18.3.1)(typescript@5.4.5)
+ expo-secure-store:
+ specifier: ^13.0.1
+ version: 13.0.1(expo@51.0.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8))
expo-splash-screen:
specifier: ~0.27.4
version: 0.27.4(expo-modules-autolinking@1.11.1)(expo@51.0.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8))
expo-status-bar:
specifier: ~1.12.1
version: 1.12.1
+ expo-web-browser:
+ specifier: ^13.0.3
+ version: 13.0.3(expo@51.0.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8))
nativewind:
specifier: ~4.0.36
version: 4.0.36(@babel/core@7.24.7)(react-native-reanimated@3.10.1(@babel/core@7.24.7)(react-native@0.74.1(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.10.1(react-native@0.74.1(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1))(react@18.3.1))(react-native@0.74.1(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.3(ts-node@10.9.2(@types/node@20.12.9)(typescript@5.4.5)))
@@ -315,6 +321,9 @@ importers:
'@acme/db':
specifier: workspace:*
version: link:../db
+ '@auth/core':
+ specifier: 0.31.0
+ version: 0.31.0
'@auth/drizzle-adapter':
specifier: ^1.1.0
version: 1.1.0
@@ -4465,6 +4474,11 @@ packages:
react-native-reanimated:
optional: true
+ expo-secure-store@13.0.1:
+ resolution: {integrity: sha512-5DTKjbv98X7yPbm+1jER/sOEIlt2Ih7qwabTvkWDXry5bPcQGoulxH5zIX9+JvVH7of8GI4t7NSEbpAO3P7FZA==}
+ peerDependencies:
+ expo: '*'
+
expo-splash-screen@0.27.4:
resolution: {integrity: sha512-JwepK1FjbwiOK2nwIFanfzj9s7UXYnpTwLX8A9v7Ec3K4V28yu8HooSc9X60cftBw9UZrs8Gwj4PgTpQabBS9A==}
peerDependencies:
@@ -4478,6 +4492,11 @@ packages:
peerDependencies:
expo: '*'
+ expo-web-browser@13.0.3:
+ resolution: {integrity: sha512-HXb7y82ApVJtqk8tManyudtTrCtx8xcUnVzmJECeHCB0SsWSQ+penVLZxJkcyATWoJOsFMnfVSVdrTcpKKGszQ==}
+ peerDependencies:
+ expo: '*'
+
expo@51.0.2:
resolution: {integrity: sha512-aRKrheMMQBcNDg2SBjW5kcSN5G58bdIpsxeSQ65Bx18DFLXjPv5UaU9kzIWRAcxaPtgictn9ut9IJQVZKChNxQ==}
hasBin: true
@@ -12561,6 +12580,10 @@ snapshots:
- supports-color
- typescript
+ expo-secure-store@13.0.1(expo@51.0.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)):
+ dependencies:
+ expo: 51.0.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)
+
expo-splash-screen@0.27.4(expo-modules-autolinking@1.11.1)(expo@51.0.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)):
dependencies:
'@expo/prebuild-config': 7.0.3(expo-modules-autolinking@1.11.1)
@@ -12576,6 +12599,10 @@ snapshots:
dependencies:
expo: 51.0.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)
+ expo-web-browser@13.0.3(expo@51.0.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)):
+ dependencies:
+ expo: 51.0.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)
+
expo@51.0.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8):
dependencies:
'@babel/runtime': 7.24.7