Complete rewrite of Scry using TypeScript stack:
- Expo/React Native with Expo Router (file-based routing)
- Convex backend (serverless functions + real-time database)
- Adaptive camera system (expo-camera in Expo Go, Vision Camera in production)
- React Native Skia + fast-opencv for image processing
- GDPR-compliant auth setup with Zitadel OIDC (pending configuration)
Key features:
- Card recognition pipeline ported to TypeScript
- Perceptual hashing (192-bit color pHash)
- CLAHE preprocessing for lighting normalization
- Local SQLite cache with Convex sync
- Collection management with offline support
Removes all .NET/MAUI code (src/, test/, tools/).
💘 Generated with Crush
Assisted-by: Claude Opus 4.5 via Crush <crush@charm.land>
48 lines
1.4 KiB
TypeScript
48 lines
1.4 KiB
TypeScript
/**
|
|
* Convex Auth configuration with Zitadel OIDC.
|
|
*
|
|
* GDPR Compliance: No user profile data (name, email, image) is stored.
|
|
* User details must be fetched from Zitadel userinfo endpoint when needed.
|
|
*/
|
|
|
|
import Zitadel from "@auth/core/providers/zitadel";
|
|
import { convexAuth } from "@convex-dev/auth/server";
|
|
|
|
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
|
|
providers: [
|
|
Zitadel({
|
|
issuer: process.env.AUTH_ZITADEL_ISSUER,
|
|
clientId: process.env.AUTH_ZITADEL_ID,
|
|
clientSecret: process.env.AUTH_ZITADEL_SECRET,
|
|
// Strip all profile data - return only the subject ID
|
|
profile(zitadelProfile) {
|
|
return {
|
|
id: zitadelProfile.sub,
|
|
// Intentionally omit: name, email, image
|
|
// These must be fetched from Zitadel userinfo endpoint
|
|
};
|
|
},
|
|
}),
|
|
],
|
|
callbacks: {
|
|
// Validate redirect URIs for React Native
|
|
async redirect({ redirectTo }) {
|
|
const allowedPrefixes = [
|
|
"scry://", // App custom scheme
|
|
"app://", // Default Expo scheme
|
|
"exp://", // Expo Go
|
|
"http://localhost",
|
|
"https://localhost",
|
|
];
|
|
|
|
const isAllowed = allowedPrefixes.some((prefix) =>
|
|
redirectTo.startsWith(prefix)
|
|
);
|
|
|
|
if (!isAllowed) {
|
|
throw new Error(`Invalid redirectTo URI: ${redirectTo}`);
|
|
}
|
|
return redirectTo;
|
|
},
|
|
},
|
|
});
|