@ttt-productions/auth-core 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/AuthProvider.d.ts +5 -5
- package/dist/react/AuthProvider.d.ts.map +1 -1
- package/dist/react/AuthProvider.js +4 -119
- package/dist/react/AuthProvider.js.map +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/types.d.ts +3 -37
- package/dist/react/types.d.ts.map +1 -1
- package/dist/react/useAuth.d.ts +3 -3
- package/dist/react/useAuth.d.ts.map +1 -1
- package/dist/react/useAuth.js +1 -1
- package/dist/react/useAuth.js.map +1 -1
- package/dist/react/useAuthGuard.d.ts +0 -2
- package/dist/react/useAuthGuard.d.ts.map +1 -1
- package/dist/react/useAuthGuard.js +1 -7
- package/dist/react/useAuthGuard.js.map +1 -1
- package/dist/server/assertAuth.d.ts +3 -0
- package/dist/server/assertAuth.d.ts.map +1 -0
- package/dist/server/assertAuth.js +108 -0
- package/dist/server/assertAuth.js.map +1 -0
- package/dist/server/assertAuth.test.d.ts +2 -0
- package/dist/server/assertAuth.test.d.ts.map +1 -0
- package/dist/server/assertAuth.test.js +407 -0
- package/dist/server/assertAuth.test.js.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +5 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/types.d.ts +141 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +7 -0
- package/dist/server/types.js.map +1 -0
- package/package.json +18 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import type { AuthProviderConfig, AuthContextValue
|
|
3
|
-
export declare const AuthContext: import("react").Context<AuthContextValue<any
|
|
4
|
-
interface AuthProviderProps<
|
|
5
|
-
config: AuthProviderConfig<
|
|
2
|
+
import type { AuthProviderConfig, AuthContextValue } from "./types.js";
|
|
3
|
+
export declare const AuthContext: import("react").Context<AuthContextValue<any> | null>;
|
|
4
|
+
interface AuthProviderProps<TClaims> {
|
|
5
|
+
config: AuthProviderConfig<TClaims>;
|
|
6
6
|
children: ReactNode;
|
|
7
7
|
}
|
|
8
|
-
export declare function AuthProvider<
|
|
8
|
+
export declare function AuthProvider<TClaims = Record<string, unknown>>({ config, children }: AuthProviderProps<TClaims>): import("react/jsx-runtime").JSX.Element;
|
|
9
9
|
export {};
|
|
10
10
|
//# sourceMappingURL=AuthProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthProvider.d.ts","sourceRoot":"","sources":["../../src/react/AuthProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"AuthProvider.d.ts","sourceRoot":"","sources":["../../src/react/AuthProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGvE,eAAO,MAAM,WAAW,uDAAoD,CAAC;AAE7E,UAAU,iBAAiB,CAAC,OAAO;IACjC,MAAM,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACpC,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,iBAAiB,CAAC,OAAO,CAAC,2CA6EjD"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { createContext, useEffect, useRef, useState, useCallback, useMemo, } from "react";
|
|
4
|
-
import { doc, onSnapshot } from "firebase/firestore";
|
|
5
4
|
import { useAuthState } from "./useAuthState.js";
|
|
6
5
|
import { getIdTokenClaims } from "../claims.js";
|
|
7
6
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -10,13 +9,6 @@ export function AuthProvider({ config, children }) {
|
|
|
10
9
|
const { user, loading: authLoading } = useAuthState(config.auth);
|
|
11
10
|
const [claims, setClaims] = useState(config.defaultClaims);
|
|
12
11
|
const [claimsLoading, setClaimsLoading] = useState(true);
|
|
13
|
-
const [userProfile, setUserProfile] = useState(null);
|
|
14
|
-
const [profileLoading, setProfileLoading] = useState(true);
|
|
15
|
-
const [profileError, setProfileError] = useState(null);
|
|
16
|
-
// Refs for cleanup
|
|
17
|
-
const profileUnsubRef = useRef(null);
|
|
18
|
-
const timeoutRef = useRef(null);
|
|
19
|
-
const prevUidRef = useRef(null);
|
|
20
12
|
// Stable config ref so effects don't re-fire on config object identity changes
|
|
21
13
|
const configRef = useRef(config);
|
|
22
14
|
configRef.current = config;
|
|
@@ -27,10 +19,12 @@ export function AuthProvider({ config, children }) {
|
|
|
27
19
|
if (!user) {
|
|
28
20
|
setClaims(configRef.current.defaultClaims);
|
|
29
21
|
setClaimsLoading(false);
|
|
22
|
+
configRef.current.onAuthStateChange?.(null);
|
|
30
23
|
return;
|
|
31
24
|
}
|
|
32
25
|
let cancelled = false;
|
|
33
26
|
setClaimsLoading(true);
|
|
27
|
+
configRef.current.onAuthStateChange?.(user);
|
|
34
28
|
getIdTokenClaims(user)
|
|
35
29
|
.then((raw) => {
|
|
36
30
|
if (cancelled)
|
|
@@ -49,102 +43,6 @@ export function AuthProvider({ config, children }) {
|
|
|
49
43
|
});
|
|
50
44
|
return () => { cancelled = true; };
|
|
51
45
|
}, [user, authLoading]);
|
|
52
|
-
// --- Profile listener ---
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
if (authLoading)
|
|
55
|
-
return;
|
|
56
|
-
const uid = user?.uid ?? null;
|
|
57
|
-
const prevUid = prevUidRef.current;
|
|
58
|
-
prevUidRef.current = uid;
|
|
59
|
-
// Clean up previous listener + timeout if user changed
|
|
60
|
-
if (uid !== prevUid) {
|
|
61
|
-
if (profileUnsubRef.current) {
|
|
62
|
-
profileUnsubRef.current();
|
|
63
|
-
profileUnsubRef.current = null;
|
|
64
|
-
}
|
|
65
|
-
if (timeoutRef.current) {
|
|
66
|
-
clearTimeout(timeoutRef.current);
|
|
67
|
-
timeoutRef.current = null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
// User signed out
|
|
71
|
-
if (!uid) {
|
|
72
|
-
setUserProfile(null);
|
|
73
|
-
setProfileError(null);
|
|
74
|
-
setProfileLoading(false);
|
|
75
|
-
configRef.current.onAuthStateChange?.(null);
|
|
76
|
-
configRef.current.onProfileChange?.(null, null);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
// User signed in (or changed)
|
|
80
|
-
setProfileLoading(true);
|
|
81
|
-
setProfileError(null);
|
|
82
|
-
configRef.current.onAuthStateChange?.(user);
|
|
83
|
-
const timeout = (configRef.current.profileTimeout ?? 15) * 1000;
|
|
84
|
-
const [colPath, docId] = configRef.current.profilePath(uid);
|
|
85
|
-
const docRef = doc(configRef.current.db, colPath, docId);
|
|
86
|
-
// Start timeout timer
|
|
87
|
-
timeoutRef.current = setTimeout(() => {
|
|
88
|
-
// Only fire if we're still waiting (profileLoading is still true)
|
|
89
|
-
setProfileError({
|
|
90
|
-
code: "PROFILE_TIMEOUT",
|
|
91
|
-
message: `Profile document not found after ${configRef.current.profileTimeout ?? 15}s.`,
|
|
92
|
-
uid,
|
|
93
|
-
});
|
|
94
|
-
setProfileLoading(false);
|
|
95
|
-
}, timeout);
|
|
96
|
-
const unsub = onSnapshot(docRef, (snap) => {
|
|
97
|
-
// Clear timeout on any successful snapshot
|
|
98
|
-
if (timeoutRef.current) {
|
|
99
|
-
clearTimeout(timeoutRef.current);
|
|
100
|
-
timeoutRef.current = null;
|
|
101
|
-
}
|
|
102
|
-
if (snap.exists()) {
|
|
103
|
-
const data = snap.data();
|
|
104
|
-
const parsed = configRef.current.parseProfile
|
|
105
|
-
? configRef.current.parseProfile(data, uid)
|
|
106
|
-
: { uid, ...data };
|
|
107
|
-
setUserProfile(parsed);
|
|
108
|
-
setProfileError(null);
|
|
109
|
-
setProfileLoading(false);
|
|
110
|
-
configRef.current.onProfileChange?.(parsed, user);
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
// Doc doesn't exist yet — restart timeout if not already timed out
|
|
114
|
-
if (!timeoutRef.current) {
|
|
115
|
-
timeoutRef.current = setTimeout(() => {
|
|
116
|
-
setProfileError({
|
|
117
|
-
code: "PROFILE_NOT_FOUND",
|
|
118
|
-
message: "Profile document does not exist.",
|
|
119
|
-
uid,
|
|
120
|
-
});
|
|
121
|
-
setProfileLoading(false);
|
|
122
|
-
}, timeout);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}, (err) => {
|
|
126
|
-
if (timeoutRef.current) {
|
|
127
|
-
clearTimeout(timeoutRef.current);
|
|
128
|
-
timeoutRef.current = null;
|
|
129
|
-
}
|
|
130
|
-
configRef.current.onError?.(err, "profile onSnapshot");
|
|
131
|
-
setProfileError({
|
|
132
|
-
code: "PROFILE_LISTENER_ERROR",
|
|
133
|
-
message: err instanceof Error ? err.message : "Profile listener failed.",
|
|
134
|
-
uid,
|
|
135
|
-
});
|
|
136
|
-
setProfileLoading(false);
|
|
137
|
-
});
|
|
138
|
-
profileUnsubRef.current = unsub;
|
|
139
|
-
return () => {
|
|
140
|
-
unsub();
|
|
141
|
-
profileUnsubRef.current = null;
|
|
142
|
-
if (timeoutRef.current) {
|
|
143
|
-
clearTimeout(timeoutRef.current);
|
|
144
|
-
timeoutRef.current = null;
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
}, [user, authLoading]);
|
|
148
46
|
// --- refreshClaims ---
|
|
149
47
|
const refreshClaims = useCallback(async () => {
|
|
150
48
|
if (!user)
|
|
@@ -158,29 +56,16 @@ export function AuthProvider({ config, children }) {
|
|
|
158
56
|
}
|
|
159
57
|
}, [user]);
|
|
160
58
|
// --- Context value ---
|
|
161
|
-
const loading = authLoading || claimsLoading
|
|
59
|
+
const loading = authLoading || claimsLoading;
|
|
162
60
|
const value = useMemo(() => ({
|
|
163
61
|
user,
|
|
164
|
-
userProfile,
|
|
165
62
|
claims,
|
|
166
63
|
loading,
|
|
167
64
|
authLoading,
|
|
168
65
|
claimsLoading,
|
|
169
|
-
profileLoading,
|
|
170
|
-
profileError,
|
|
171
66
|
isAuthenticated: !!user,
|
|
172
67
|
refreshClaims,
|
|
173
|
-
}), [
|
|
174
|
-
user,
|
|
175
|
-
userProfile,
|
|
176
|
-
claims,
|
|
177
|
-
loading,
|
|
178
|
-
authLoading,
|
|
179
|
-
claimsLoading,
|
|
180
|
-
profileLoading,
|
|
181
|
-
profileError,
|
|
182
|
-
refreshClaims,
|
|
183
|
-
]);
|
|
68
|
+
}), [user, claims, loading, authLoading, claimsLoading, refreshClaims]);
|
|
184
69
|
return (_jsx(AuthContext.Provider, { value: value, children: children }));
|
|
185
70
|
}
|
|
186
71
|
//# sourceMappingURL=AuthProvider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthProvider.js","sourceRoot":"","sources":["../../src/react/AuthProvider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EACL,aAAa,EACb,SAAS,EACT,MAAM,EACN,QAAQ,EACR,WAAW,EACX,OAAO,GAER,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"AuthProvider.js","sourceRoot":"","sources":["../../src/react/AuthProvider.tsx"],"names":[],"mappings":"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"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { AuthState } from "./useAuthState.js";
|
|
2
2
|
export { useAuthState } from "./useAuthState.js";
|
|
3
|
-
export type {
|
|
3
|
+
export type { AuthProviderConfig, AuthContextValue } from "./types.js";
|
|
4
4
|
export { AuthProvider } from "./AuthProvider.js";
|
|
5
5
|
export { AuthContext } from "./AuthProvider.js";
|
|
6
6
|
export { useAuth } from "./useAuth.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/react/types.d.ts
CHANGED
|
@@ -1,62 +1,28 @@
|
|
|
1
1
|
import type { Auth, User } from "firebase/auth";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Base profile contract. Apps extend with their own fields.
|
|
5
|
-
* auth-core uses these for monitoring integration and generic display.
|
|
6
|
-
*/
|
|
7
|
-
export interface BaseProfile {
|
|
8
|
-
uid: string;
|
|
9
|
-
displayName: string;
|
|
10
|
-
photoURL?: string | null;
|
|
11
|
-
status?: string;
|
|
12
|
-
}
|
|
13
|
-
export interface AuthProviderConfig<TProfile extends BaseProfile = BaseProfile, TClaims = Record<string, unknown>> {
|
|
2
|
+
export interface AuthProviderConfig<TClaims = Record<string, unknown>> {
|
|
14
3
|
auth: Auth;
|
|
15
|
-
db: Firestore;
|
|
16
|
-
/** Firestore path segments for profile doc: (uid) => ['users', uid] */
|
|
17
|
-
profilePath: (uid: string) => [string, string];
|
|
18
|
-
/** Transform raw Firestore data into app profile type */
|
|
19
|
-
parseProfile?: (data: Record<string, unknown>, uid: string) => TProfile;
|
|
20
4
|
/** Transform raw ID token claims into app claims shape */
|
|
21
5
|
parseClaims: (raw: Record<string, unknown>) => TClaims;
|
|
22
6
|
/** Default claims when no user or claims unavailable */
|
|
23
7
|
defaultClaims: TClaims;
|
|
24
8
|
/** Called on Firebase auth state change (for monitoring, analytics) */
|
|
25
9
|
onAuthStateChange?: (user: User | null) => void;
|
|
26
|
-
/** Called when profile snapshot updates (for monitoring, RQ cache bridge) */
|
|
27
|
-
onProfileChange?: (profile: TProfile | null, user: User | null) => void;
|
|
28
10
|
/** Called on errors (for Sentry) */
|
|
29
11
|
onError?: (error: unknown, context: string) => void;
|
|
30
|
-
/**
|
|
31
|
-
* Max seconds to wait for profile doc after auth.
|
|
32
|
-
* If exceeded, profileError is set and loading becomes false.
|
|
33
|
-
* Default: 15
|
|
34
|
-
*/
|
|
35
|
-
profileTimeout?: number;
|
|
36
12
|
}
|
|
37
|
-
export interface AuthContextValue<
|
|
13
|
+
export interface AuthContextValue<TClaims = Record<string, unknown>> {
|
|
38
14
|
/** Firebase User */
|
|
39
15
|
user: User | null;
|
|
40
|
-
/** Firestore profile doc, parsed */
|
|
41
|
-
userProfile: TProfile | null;
|
|
42
16
|
/** Parsed custom claims */
|
|
43
17
|
claims: TClaims;
|
|
44
|
-
/** True until auth + claims
|
|
18
|
+
/** True until auth + claims resolved */
|
|
45
19
|
loading: boolean;
|
|
46
20
|
/** Individual loading states */
|
|
47
21
|
authLoading: boolean;
|
|
48
22
|
claimsLoading: boolean;
|
|
49
|
-
profileLoading: boolean;
|
|
50
|
-
/** Set when user is authenticated but profile doc not found within timeout */
|
|
51
|
-
profileError: ProfileError | null;
|
|
52
23
|
/** Convenience: !!user */
|
|
53
24
|
isAuthenticated: boolean;
|
|
54
25
|
/** Force refresh claims from Firebase */
|
|
55
26
|
refreshClaims: () => Promise<void>;
|
|
56
27
|
}
|
|
57
|
-
export type ProfileError = {
|
|
58
|
-
code: "PROFILE_NOT_FOUND" | "PROFILE_TIMEOUT" | "PROFILE_LISTENER_ERROR";
|
|
59
|
-
message: string;
|
|
60
|
-
uid: string;
|
|
61
|
-
};
|
|
62
28
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,WAAW,kBAAkB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACnE,IAAI,EAAE,IAAI,CAAC;IAEX,0DAA0D;IAC1D,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IAEvD,wDAAwD;IACxD,aAAa,EAAE,OAAO,CAAC;IAEvB,uEAAuE;IACvE,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IAEhD,oCAAoC;IACpC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,gBAAgB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACjE,oBAAoB;IACpB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,2BAA2B;IAC3B,MAAM,EAAE,OAAO,CAAC;IAEhB,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAC;IAEjB,gCAAgC;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IAEvB,0BAA0B;IAC1B,eAAe,EAAE,OAAO,CAAC;IAEzB,yCAAyC;IACzC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC"}
|
package/dist/react/useAuth.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AuthContextValue } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Access the auth context. Must be used within an AuthProvider.
|
|
4
|
-
* Generic
|
|
4
|
+
* Generic param lets each app specify its own claims type.
|
|
5
5
|
*/
|
|
6
|
-
export declare function useAuth<
|
|
6
|
+
export declare function useAuth<TClaims = Record<string, unknown>>(): AuthContextValue<TClaims>;
|
|
7
7
|
//# sourceMappingURL=useAuth.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../src/react/useAuth.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../src/react/useAuth.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,gBAAgB,CAAC,OAAO,CAAC,CAMtF"}
|
package/dist/react/useAuth.js
CHANGED
|
@@ -3,7 +3,7 @@ import { useContext } from "react";
|
|
|
3
3
|
import { AuthContext } from "./AuthProvider.js";
|
|
4
4
|
/**
|
|
5
5
|
* Access the auth context. Must be used within an AuthProvider.
|
|
6
|
-
* Generic
|
|
6
|
+
* Generic param lets each app specify its own claims type.
|
|
7
7
|
*/
|
|
8
8
|
export function useAuth() {
|
|
9
9
|
const ctx = useContext(AuthContext);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAuth.js","sourceRoot":"","sources":["../../src/react/useAuth.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD;;;GAGG;AACH,MAAM,UAAU,OAAO;
|
|
1
|
+
{"version":3,"file":"useAuth.js","sourceRoot":"","sources":["../../src/react/useAuth.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD;;;GAGG;AACH,MAAM,UAAU,OAAO;IACrB,MAAM,GAAG,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACpC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,GAAgC,CAAC;AAC1C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAuthGuard.d.ts","sourceRoot":"","sources":["../../src/react/useAuthGuard.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,sEAAsE;IACtE,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,YAAY,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,mBAAmB;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,qBAAqB;IACrB,eAAe,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useAuthGuard.d.ts","sourceRoot":"","sources":["../../src/react/useAuthGuard.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,sEAAsE;IACtE,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,YAAY,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,mBAAmB;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,qBAAqB;IACrB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAID;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CA4D1D"}
|
|
@@ -10,7 +10,7 @@ const DEFAULT_REDIRECT_KEY = "auth_redirect_path";
|
|
|
10
10
|
* - Saves attempted path for post-login redirect.
|
|
11
11
|
*/
|
|
12
12
|
export function useAuthGuard(config) {
|
|
13
|
-
const { publicRoutes, authRedirectRoutes, loginRoute, defaultRoute, rootRedirect, redirectKey = DEFAULT_REDIRECT_KEY, replace, pathname, loading, isAuthenticated,
|
|
13
|
+
const { publicRoutes, authRedirectRoutes, loginRoute, defaultRoute, rootRedirect, redirectKey = DEFAULT_REDIRECT_KEY, replace, pathname, loading, isAuthenticated, } = config;
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
if (loading)
|
|
16
16
|
return;
|
|
@@ -31,11 +31,6 @@ export function useAuthGuard(config) {
|
|
|
31
31
|
}
|
|
32
32
|
// Authenticated user on auth-only route (login, register, etc.)
|
|
33
33
|
if (isAuthenticated && isAuthRedirect) {
|
|
34
|
-
// Still registering -- profile not created yet, stay on page
|
|
35
|
-
if (!hasProfile && typeof window !== "undefined" && localStorage.getItem("isRegistering") === "true") {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
// Redirect to saved path or default
|
|
39
34
|
let target = defaultRoute;
|
|
40
35
|
if (typeof window !== "undefined") {
|
|
41
36
|
const saved = localStorage.getItem(redirectKey);
|
|
@@ -49,7 +44,6 @@ export function useAuthGuard(config) {
|
|
|
49
44
|
}, [
|
|
50
45
|
loading,
|
|
51
46
|
isAuthenticated,
|
|
52
|
-
hasProfile,
|
|
53
47
|
pathname,
|
|
54
48
|
publicRoutes,
|
|
55
49
|
authRedirectRoutes,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAuthGuard.js","sourceRoot":"","sources":["../../src/react/useAuthGuard.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useAuthGuard.js","sourceRoot":"","sources":["../../src/react/useAuthGuard.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAyBlC,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,MAAuB;IAClD,MAAM,EACJ,YAAY,EACZ,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,GAAG,oBAAoB,EAClC,OAAO,EACP,QAAQ,EACR,OAAO,EACP,eAAe,GAChB,GAAG,MAAM,CAAC;IAEX,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO;YAAE,OAAO;QAEpB,6BAA6B;QAC7B,IAAI,YAAY,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;YACrC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;YACrF,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GACZ,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACjF,MAAM,cAAc,GAAG,kBAAkB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAE7D,+DAA+D;QAC/D,IAAI,CAAC,eAAe,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;gBAClC,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;YAC9C,CAAC;YACD,OAAO,CAAC,UAAU,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,gEAAgE;QAChE,IAAI,eAAe,IAAI,cAAc,EAAE,CAAC;YACtC,IAAI,MAAM,GAAG,YAAY,CAAC;YAC1B,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;gBAClC,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;gBAChD,IAAI,KAAK,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;oBACjD,MAAM,GAAG,KAAK,CAAC;gBACjB,CAAC;gBACD,YAAY,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;YACvC,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,EAAE;QACD,OAAO;QACP,eAAe;QACf,QAAQ;QACR,YAAY;QACZ,kBAAkB;QAClB,UAAU;QACV,YAAY;QACZ,YAAY;QACZ,WAAW;QACX,OAAO;KACR,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assertAuth.d.ts","sourceRoot":"","sources":["../../src/server/assertAuth.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EAGb,MAAM,YAAY,CAAC;AAEpB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,QAAQ,EAC9C,MAAM,EAAE,gBAAgB,CAAC,KAAK,EAAE,QAAQ,CAAC,GACxC,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,CAwH/B"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// packages/auth-core/src/server/assertAuth.ts
|
|
2
|
+
//
|
|
3
|
+
// Factory that produces an assertAuth function bound to a consuming app's
|
|
4
|
+
// Firestore paths, user/project shapes, and admin check.
|
|
5
|
+
//
|
|
6
|
+
// Every Cloud Functions callable in the consuming app should invoke the
|
|
7
|
+
// returned assertAuth as its first executable line. The check order is:
|
|
8
|
+
// 1. Authentication
|
|
9
|
+
// 2. Email verification
|
|
10
|
+
// 3. Parallel Firestore reads (user doc + project doc, as needed)
|
|
11
|
+
// 4. Status check (banned / disabled)
|
|
12
|
+
// 5. Creator check
|
|
13
|
+
// 6. Project membership check
|
|
14
|
+
// 7. Admin check (delegated to config.requireAdmin)
|
|
15
|
+
import { HttpsError } from "firebase-functions/v2/https";
|
|
16
|
+
export function createAssertAuth(config) {
|
|
17
|
+
return async function assertAuth(request, requirements = {}) {
|
|
18
|
+
// 1. Authentication check (always first)
|
|
19
|
+
if (!request.auth) {
|
|
20
|
+
throw new HttpsError("unauthenticated", "You must be authenticated");
|
|
21
|
+
}
|
|
22
|
+
const uid = request.auth.uid;
|
|
23
|
+
const token = request.auth.token;
|
|
24
|
+
// 2. Email verification check
|
|
25
|
+
if (requirements.emailVerified === true &&
|
|
26
|
+
requirements.allowUnverified !== true) {
|
|
27
|
+
if (token.email_verified !== true) {
|
|
28
|
+
throw new HttpsError("failed-precondition", "Email must be verified");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// 3. Determine which Firestore reads are needed
|
|
32
|
+
const needsUserDoc = requirements.creator === true || requirements.allowAnyStatus !== true;
|
|
33
|
+
const needsProject = requirements.projectMembership !== undefined;
|
|
34
|
+
const db = config.firestore();
|
|
35
|
+
const [userSnap, projectSnap] = await Promise.all([
|
|
36
|
+
needsUserDoc
|
|
37
|
+
? db.doc(config.userProfilePath(uid)).get()
|
|
38
|
+
: Promise.resolve(null),
|
|
39
|
+
needsProject
|
|
40
|
+
? db.doc(config.projectPath(requirements.projectMembership.projectId)).get()
|
|
41
|
+
: Promise.resolve(null),
|
|
42
|
+
]);
|
|
43
|
+
let userDoc;
|
|
44
|
+
// 4. Status check (default: block banned + disabled; skipped if allowAnyStatus)
|
|
45
|
+
if (requirements.allowAnyStatus !== true) {
|
|
46
|
+
if (!userSnap || !userSnap.exists) {
|
|
47
|
+
throw new HttpsError("not-found", "User profile not found");
|
|
48
|
+
}
|
|
49
|
+
const userData = userSnap.data();
|
|
50
|
+
const status = config.getUserStatus(userData);
|
|
51
|
+
if (status === "disabled") {
|
|
52
|
+
throw new HttpsError("permission-denied", "Account is not in good standing");
|
|
53
|
+
}
|
|
54
|
+
if (status === "banned" && requirements.allowBanned !== true) {
|
|
55
|
+
throw new HttpsError("permission-denied", "Account is not in good standing");
|
|
56
|
+
}
|
|
57
|
+
userDoc = userData;
|
|
58
|
+
}
|
|
59
|
+
// 5. Creator check
|
|
60
|
+
if (requirements.creator === true) {
|
|
61
|
+
if (!userDoc) {
|
|
62
|
+
// allowAnyStatus is true — user doc was still fetched, validate now
|
|
63
|
+
if (!userSnap || !userSnap.exists) {
|
|
64
|
+
throw new HttpsError("not-found", "User profile not found");
|
|
65
|
+
}
|
|
66
|
+
userDoc = userSnap.data();
|
|
67
|
+
}
|
|
68
|
+
if (!config.isUserCreator(userDoc)) {
|
|
69
|
+
throw new HttpsError("failed-precondition", "Creator status required");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// 6. Project membership check
|
|
73
|
+
let projectContext;
|
|
74
|
+
if (requirements.projectMembership !== undefined) {
|
|
75
|
+
if (!projectSnap || !projectSnap.exists) {
|
|
76
|
+
throw new HttpsError("not-found", "Project not found");
|
|
77
|
+
}
|
|
78
|
+
const projectData = projectSnap.data();
|
|
79
|
+
const isOwner = config.isProjectOwner(projectData, uid);
|
|
80
|
+
const isMember = isOwner || config.isProjectMember(projectData, uid);
|
|
81
|
+
if (requirements.projectMembership.role === "owner") {
|
|
82
|
+
if (!isOwner) {
|
|
83
|
+
throw new HttpsError("permission-denied", "Project owner access required");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
if (!isMember) {
|
|
88
|
+
throw new HttpsError("permission-denied", "Not a member of this project");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
projectContext = { data: projectData, isOwner, isMember };
|
|
92
|
+
}
|
|
93
|
+
// 7. Admin check (delegates to config.requireAdmin)
|
|
94
|
+
if (requirements.admin !== undefined) {
|
|
95
|
+
await config.requireAdmin(uid, token, requirements.admin);
|
|
96
|
+
}
|
|
97
|
+
// 8. Return context with whatever docs were fetched
|
|
98
|
+
const ctx = { uid, token };
|
|
99
|
+
if (userDoc !== undefined) {
|
|
100
|
+
ctx.userDoc = userDoc;
|
|
101
|
+
}
|
|
102
|
+
if (projectContext !== undefined) {
|
|
103
|
+
ctx.project = projectContext;
|
|
104
|
+
}
|
|
105
|
+
return ctx;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=assertAuth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assertAuth.js","sourceRoot":"","sources":["../../src/server/assertAuth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assertAuth.test.d.ts","sourceRoot":"","sources":["../../src/server/assertAuth.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
vi.mock("firebase-functions/v2/https", () => {
|
|
3
|
+
class HttpsError extends Error {
|
|
4
|
+
code;
|
|
5
|
+
constructor(code, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.name = "HttpsError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return { HttpsError };
|
|
12
|
+
});
|
|
13
|
+
import { createAssertAuth } from "./assertAuth.js";
|
|
14
|
+
function makeRequest(opts) {
|
|
15
|
+
const uid = opts?.uid ?? "uid-123";
|
|
16
|
+
return {
|
|
17
|
+
data: {},
|
|
18
|
+
rawRequest: {},
|
|
19
|
+
auth: {
|
|
20
|
+
uid,
|
|
21
|
+
token: { uid, email_verified: opts?.emailVerified ?? true },
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function makeUnauthRequest() {
|
|
26
|
+
return { data: {}, rawRequest: {}, auth: undefined };
|
|
27
|
+
}
|
|
28
|
+
const activeUserDoc = {
|
|
29
|
+
uid: "uid-123",
|
|
30
|
+
displayName: "Test User",
|
|
31
|
+
status: "active",
|
|
32
|
+
};
|
|
33
|
+
const creatorUserDoc = {
|
|
34
|
+
...activeUserDoc,
|
|
35
|
+
creator: "creator-12345",
|
|
36
|
+
};
|
|
37
|
+
function memberProjectDoc(callerUid) {
|
|
38
|
+
return {
|
|
39
|
+
projectId: "p1",
|
|
40
|
+
ownedBy: { uid: "owner-uid" },
|
|
41
|
+
activeUserIds: { [callerUid]: true },
|
|
42
|
+
activeUsers: [],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// --- harness ---------------------------------------------------------------
|
|
46
|
+
let mockUserGet;
|
|
47
|
+
let mockProjectGet;
|
|
48
|
+
let mockRequireAdmin;
|
|
49
|
+
let mockDbDoc;
|
|
50
|
+
let assertAuth;
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
mockUserGet = vi.fn().mockResolvedValue({ exists: true, data: () => activeUserDoc });
|
|
53
|
+
mockProjectGet = vi.fn().mockResolvedValue({
|
|
54
|
+
exists: true,
|
|
55
|
+
data: () => memberProjectDoc("uid-123"),
|
|
56
|
+
});
|
|
57
|
+
mockRequireAdmin = vi.fn().mockResolvedValue(undefined);
|
|
58
|
+
const userDocRef = { get: mockUserGet };
|
|
59
|
+
const projectDocRef = { get: mockProjectGet };
|
|
60
|
+
mockDbDoc = vi.fn((path) => {
|
|
61
|
+
if (path.startsWith("allProjects/"))
|
|
62
|
+
return projectDocRef;
|
|
63
|
+
return userDocRef;
|
|
64
|
+
});
|
|
65
|
+
const fakeDb = { doc: mockDbDoc };
|
|
66
|
+
const config = {
|
|
67
|
+
firestore: () => fakeDb,
|
|
68
|
+
userProfilePath: (uid) => `userProfiles/${uid}`,
|
|
69
|
+
projectPath: (projectId) => `allProjects/${projectId}`,
|
|
70
|
+
getUserStatus: (user) => {
|
|
71
|
+
if (user.status === "disabled")
|
|
72
|
+
return "disabled";
|
|
73
|
+
if (user.status === "banned")
|
|
74
|
+
return "banned";
|
|
75
|
+
return "ok";
|
|
76
|
+
},
|
|
77
|
+
isUserCreator: (user) => Boolean(user.creator),
|
|
78
|
+
isProjectOwner: (project, uid) => project.ownedBy?.uid === uid,
|
|
79
|
+
isProjectMember: (project, uid) => project.ownedBy?.uid === uid ||
|
|
80
|
+
project.activeUserIds?.[uid] === true ||
|
|
81
|
+
(project.activeUsers || []).some((u) => u.uid === uid),
|
|
82
|
+
requireAdmin: mockRequireAdmin,
|
|
83
|
+
};
|
|
84
|
+
assertAuth = createAssertAuth(config);
|
|
85
|
+
});
|
|
86
|
+
// --- tests -----------------------------------------------------------------
|
|
87
|
+
describe("Authentication", () => {
|
|
88
|
+
it("throws unauthenticated when request.auth is absent", async () => {
|
|
89
|
+
await expect(assertAuth(makeUnauthRequest())).rejects.toMatchObject({
|
|
90
|
+
code: "unauthenticated",
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
it("succeeds with no Firestore reads when only allowAnyStatus is set", async () => {
|
|
94
|
+
const ctx = await assertAuth(makeRequest(), { allowAnyStatus: true });
|
|
95
|
+
expect(ctx.uid).toBe("uid-123");
|
|
96
|
+
expect(ctx.userDoc).toBeUndefined();
|
|
97
|
+
expect(ctx.project).toBeUndefined();
|
|
98
|
+
expect(mockDbDoc).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe("Email verification", () => {
|
|
102
|
+
it("throws failed-precondition when emailVerified required and token email_verified is false", async () => {
|
|
103
|
+
await expect(assertAuth(makeRequest({ emailVerified: false }), {
|
|
104
|
+
emailVerified: true,
|
|
105
|
+
allowAnyStatus: true,
|
|
106
|
+
})).rejects.toMatchObject({ code: "failed-precondition" });
|
|
107
|
+
});
|
|
108
|
+
it("succeeds when emailVerified required and token email_verified is true", async () => {
|
|
109
|
+
await expect(assertAuth(makeRequest({ emailVerified: true }), {
|
|
110
|
+
emailVerified: true,
|
|
111
|
+
allowAnyStatus: true,
|
|
112
|
+
})).resolves.toMatchObject({ uid: "uid-123" });
|
|
113
|
+
});
|
|
114
|
+
it("succeeds when allowUnverified is true even if email_verified is false", async () => {
|
|
115
|
+
await expect(assertAuth(makeRequest({ emailVerified: false }), {
|
|
116
|
+
emailVerified: true,
|
|
117
|
+
allowUnverified: true,
|
|
118
|
+
allowAnyStatus: true,
|
|
119
|
+
})).resolves.toMatchObject({ uid: "uid-123" });
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe("Not-banned check (default behavior)", () => {
|
|
123
|
+
it("throws permission-denied when user status is banned", async () => {
|
|
124
|
+
mockUserGet.mockResolvedValue({
|
|
125
|
+
exists: true,
|
|
126
|
+
data: () => ({ ...activeUserDoc, status: "banned" }),
|
|
127
|
+
});
|
|
128
|
+
await expect(assertAuth(makeRequest())).rejects.toMatchObject({
|
|
129
|
+
code: "permission-denied",
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
it("throws permission-denied when user status is disabled", async () => {
|
|
133
|
+
mockUserGet.mockResolvedValue({
|
|
134
|
+
exists: true,
|
|
135
|
+
data: () => ({ ...activeUserDoc, status: "disabled" }),
|
|
136
|
+
});
|
|
137
|
+
await expect(assertAuth(makeRequest())).rejects.toMatchObject({
|
|
138
|
+
code: "permission-denied",
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
it("throws not-found when user doc does not exist", async () => {
|
|
142
|
+
mockUserGet.mockResolvedValue({ exists: false, data: () => null });
|
|
143
|
+
await expect(assertAuth(makeRequest())).rejects.toMatchObject({
|
|
144
|
+
code: "not-found",
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
it("succeeds when user doc has no status field", async () => {
|
|
148
|
+
mockUserGet.mockResolvedValue({
|
|
149
|
+
exists: true,
|
|
150
|
+
data: () => ({ uid: "uid-123", displayName: "Test" }),
|
|
151
|
+
});
|
|
152
|
+
const ctx = await assertAuth(makeRequest());
|
|
153
|
+
expect(ctx.uid).toBe("uid-123");
|
|
154
|
+
expect(ctx.userDoc).toBeDefined();
|
|
155
|
+
});
|
|
156
|
+
it("succeeds when user status is active", async () => {
|
|
157
|
+
const ctx = await assertAuth(makeRequest());
|
|
158
|
+
expect(ctx.uid).toBe("uid-123");
|
|
159
|
+
expect(ctx.userDoc).toMatchObject({ status: "active" });
|
|
160
|
+
});
|
|
161
|
+
it("skips user doc read entirely when allowAnyStatus is true", async () => {
|
|
162
|
+
await assertAuth(makeRequest(), { allowAnyStatus: true });
|
|
163
|
+
expect(mockUserGet).not.toHaveBeenCalled();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
describe("allowBanned opt-in", () => {
|
|
167
|
+
it("allows a banned user when allowBanned is true", async () => {
|
|
168
|
+
mockUserGet.mockResolvedValue({
|
|
169
|
+
exists: true,
|
|
170
|
+
data: () => ({ ...activeUserDoc, status: "banned" }),
|
|
171
|
+
});
|
|
172
|
+
const ctx = await assertAuth(makeRequest(), { allowBanned: true });
|
|
173
|
+
expect(ctx.uid).toBe("uid-123");
|
|
174
|
+
expect(ctx.userDoc).toMatchObject({ status: "banned" });
|
|
175
|
+
});
|
|
176
|
+
it("still blocks a disabled user even when allowBanned is true", async () => {
|
|
177
|
+
mockUserGet.mockResolvedValue({
|
|
178
|
+
exists: true,
|
|
179
|
+
data: () => ({ ...activeUserDoc, status: "disabled" }),
|
|
180
|
+
});
|
|
181
|
+
await expect(assertAuth(makeRequest(), { allowBanned: true })).rejects.toMatchObject({ code: "permission-denied" });
|
|
182
|
+
});
|
|
183
|
+
it("still requires the user doc to exist when allowBanned is true", async () => {
|
|
184
|
+
mockUserGet.mockResolvedValue({ exists: false, data: () => null });
|
|
185
|
+
await expect(assertAuth(makeRequest(), { allowBanned: true })).rejects.toMatchObject({ code: "not-found" });
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
describe("Creator check", () => {
|
|
189
|
+
it("throws failed-precondition when user has no creator field", async () => {
|
|
190
|
+
mockUserGet.mockResolvedValue({
|
|
191
|
+
exists: true,
|
|
192
|
+
data: () => ({ ...activeUserDoc }),
|
|
193
|
+
});
|
|
194
|
+
await expect(assertAuth(makeRequest(), { creator: true, allowAnyStatus: true })).rejects.toMatchObject({ code: "failed-precondition" });
|
|
195
|
+
});
|
|
196
|
+
it("succeeds when user has a truthy creator string", async () => {
|
|
197
|
+
mockUserGet.mockResolvedValue({
|
|
198
|
+
exists: true,
|
|
199
|
+
data: () => creatorUserDoc,
|
|
200
|
+
});
|
|
201
|
+
const ctx = await assertAuth(makeRequest(), {
|
|
202
|
+
creator: true,
|
|
203
|
+
allowAnyStatus: true,
|
|
204
|
+
});
|
|
205
|
+
expect(ctx.userDoc).toMatchObject({ creator: "creator-12345" });
|
|
206
|
+
});
|
|
207
|
+
it("throws not-found when user doc is missing and creator check runs", async () => {
|
|
208
|
+
mockUserGet.mockResolvedValue({ exists: false, data: () => null });
|
|
209
|
+
await expect(assertAuth(makeRequest(), { creator: true, allowAnyStatus: true })).rejects.toMatchObject({ code: "not-found" });
|
|
210
|
+
});
|
|
211
|
+
it("throws permission-denied (not failed-precondition) when user is banned and creator=true", async () => {
|
|
212
|
+
mockUserGet.mockResolvedValue({
|
|
213
|
+
exists: true,
|
|
214
|
+
data: () => ({ ...creatorUserDoc, status: "banned" }),
|
|
215
|
+
});
|
|
216
|
+
// banned check in step 4 fires before creator check in step 5
|
|
217
|
+
await expect(assertAuth(makeRequest(), { creator: true })).rejects.toMatchObject({ code: "permission-denied" });
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
describe("Project membership", () => {
|
|
221
|
+
it("throws not-found when project doc does not exist", async () => {
|
|
222
|
+
mockProjectGet.mockResolvedValue({ exists: false, data: () => null });
|
|
223
|
+
await expect(assertAuth(makeRequest(), {
|
|
224
|
+
projectMembership: { projectId: "p1" },
|
|
225
|
+
allowAnyStatus: true,
|
|
226
|
+
})).rejects.toMatchObject({ code: "not-found" });
|
|
227
|
+
});
|
|
228
|
+
it("throws permission-denied when caller is not in any membership field", async () => {
|
|
229
|
+
mockProjectGet.mockResolvedValue({
|
|
230
|
+
exists: true,
|
|
231
|
+
data: () => ({
|
|
232
|
+
projectId: "p1",
|
|
233
|
+
ownedBy: { uid: "other-uid" },
|
|
234
|
+
activeUserIds: { "another-uid": true },
|
|
235
|
+
activeUsers: [{ uid: "yet-another-uid" }],
|
|
236
|
+
}),
|
|
237
|
+
});
|
|
238
|
+
await expect(assertAuth(makeRequest(), {
|
|
239
|
+
projectMembership: { projectId: "p1" },
|
|
240
|
+
allowAnyStatus: true,
|
|
241
|
+
})).rejects.toMatchObject({ code: "permission-denied" });
|
|
242
|
+
});
|
|
243
|
+
it("succeeds and sets isMember=true, isOwner=false when caller is in activeUserIds", async () => {
|
|
244
|
+
mockProjectGet.mockResolvedValue({
|
|
245
|
+
exists: true,
|
|
246
|
+
data: () => ({
|
|
247
|
+
projectId: "p1",
|
|
248
|
+
ownedBy: { uid: "owner-uid" },
|
|
249
|
+
activeUserIds: { "uid-123": true },
|
|
250
|
+
activeUsers: [],
|
|
251
|
+
}),
|
|
252
|
+
});
|
|
253
|
+
const ctx = await assertAuth(makeRequest(), {
|
|
254
|
+
projectMembership: { projectId: "p1" },
|
|
255
|
+
allowAnyStatus: true,
|
|
256
|
+
});
|
|
257
|
+
expect(ctx.project?.isMember).toBe(true);
|
|
258
|
+
expect(ctx.project?.isOwner).toBe(false);
|
|
259
|
+
});
|
|
260
|
+
it("succeeds when caller is in activeUsers array but not in activeUserIds", async () => {
|
|
261
|
+
mockProjectGet.mockResolvedValue({
|
|
262
|
+
exists: true,
|
|
263
|
+
data: () => ({
|
|
264
|
+
projectId: "p1",
|
|
265
|
+
ownedBy: { uid: "owner-uid" },
|
|
266
|
+
activeUserIds: {},
|
|
267
|
+
activeUsers: [{ uid: "uid-123" }],
|
|
268
|
+
}),
|
|
269
|
+
});
|
|
270
|
+
const ctx = await assertAuth(makeRequest(), {
|
|
271
|
+
projectMembership: { projectId: "p1" },
|
|
272
|
+
allowAnyStatus: true,
|
|
273
|
+
});
|
|
274
|
+
expect(ctx.project?.isMember).toBe(true);
|
|
275
|
+
expect(ctx.project?.isOwner).toBe(false);
|
|
276
|
+
});
|
|
277
|
+
it("succeeds and sets isOwner=true, isMember=true when caller is ownedBy.uid", async () => {
|
|
278
|
+
mockProjectGet.mockResolvedValue({
|
|
279
|
+
exists: true,
|
|
280
|
+
data: () => ({
|
|
281
|
+
projectId: "p1",
|
|
282
|
+
ownedBy: { uid: "uid-123" },
|
|
283
|
+
activeUserIds: {},
|
|
284
|
+
activeUsers: [],
|
|
285
|
+
}),
|
|
286
|
+
});
|
|
287
|
+
const ctx = await assertAuth(makeRequest(), {
|
|
288
|
+
projectMembership: { projectId: "p1" },
|
|
289
|
+
allowAnyStatus: true,
|
|
290
|
+
});
|
|
291
|
+
expect(ctx.project?.isOwner).toBe(true);
|
|
292
|
+
expect(ctx.project?.isMember).toBe(true);
|
|
293
|
+
});
|
|
294
|
+
it("throws permission-denied when role is owner but caller is only a member", async () => {
|
|
295
|
+
mockProjectGet.mockResolvedValue({
|
|
296
|
+
exists: true,
|
|
297
|
+
data: () => ({
|
|
298
|
+
projectId: "p1",
|
|
299
|
+
ownedBy: { uid: "owner-uid" },
|
|
300
|
+
activeUserIds: { "uid-123": true },
|
|
301
|
+
activeUsers: [],
|
|
302
|
+
}),
|
|
303
|
+
});
|
|
304
|
+
await expect(assertAuth(makeRequest(), {
|
|
305
|
+
projectMembership: { projectId: "p1", role: "owner" },
|
|
306
|
+
allowAnyStatus: true,
|
|
307
|
+
})).rejects.toMatchObject({ code: "permission-denied" });
|
|
308
|
+
});
|
|
309
|
+
it("succeeds when role is owner and caller is the owner", async () => {
|
|
310
|
+
mockProjectGet.mockResolvedValue({
|
|
311
|
+
exists: true,
|
|
312
|
+
data: () => ({
|
|
313
|
+
projectId: "p1",
|
|
314
|
+
ownedBy: { uid: "uid-123" },
|
|
315
|
+
activeUserIds: {},
|
|
316
|
+
activeUsers: [],
|
|
317
|
+
}),
|
|
318
|
+
});
|
|
319
|
+
const ctx = await assertAuth(makeRequest(), {
|
|
320
|
+
projectMembership: { projectId: "p1", role: "owner" },
|
|
321
|
+
allowAnyStatus: true,
|
|
322
|
+
});
|
|
323
|
+
expect(ctx.project?.isOwner).toBe(true);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
describe("Admin check", () => {
|
|
327
|
+
it("propagates the error when requireAdmin throws", async () => {
|
|
328
|
+
mockRequireAdmin.mockRejectedValue(Object.assign(new Error("Administrator access required"), {
|
|
329
|
+
code: "permission-denied",
|
|
330
|
+
}));
|
|
331
|
+
await expect(assertAuth(makeRequest(), {
|
|
332
|
+
admin: { allowJrAdmin: false },
|
|
333
|
+
allowAnyStatus: true,
|
|
334
|
+
})).rejects.toMatchObject({ code: "permission-denied" });
|
|
335
|
+
});
|
|
336
|
+
it("succeeds and returns ctx when requireAdmin resolves", async () => {
|
|
337
|
+
mockRequireAdmin.mockResolvedValue(undefined);
|
|
338
|
+
const ctx = await assertAuth(makeRequest(), {
|
|
339
|
+
admin: { allowJrAdmin: true },
|
|
340
|
+
allowAnyStatus: true,
|
|
341
|
+
});
|
|
342
|
+
expect(ctx.uid).toBe("uid-123");
|
|
343
|
+
expect(mockRequireAdmin).toHaveBeenCalledWith("uid-123", expect.any(Object), { allowJrAdmin: true });
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
describe("Combined checks", () => {
|
|
347
|
+
it("fails on email check first when emailVerified and creator are both required but email is unverified", async () => {
|
|
348
|
+
await expect(assertAuth(makeRequest({ emailVerified: false }), {
|
|
349
|
+
emailVerified: true,
|
|
350
|
+
creator: true,
|
|
351
|
+
})).rejects.toMatchObject({
|
|
352
|
+
code: "failed-precondition",
|
|
353
|
+
message: expect.stringContaining("Email"),
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
it("fails with creator error when email is verified but user lacks creator status", async () => {
|
|
357
|
+
mockUserGet.mockResolvedValue({ exists: true, data: () => activeUserDoc });
|
|
358
|
+
await expect(assertAuth(makeRequest({ emailVerified: true }), {
|
|
359
|
+
emailVerified: true,
|
|
360
|
+
creator: true,
|
|
361
|
+
})).rejects.toMatchObject({
|
|
362
|
+
code: "failed-precondition",
|
|
363
|
+
message: expect.stringContaining("Creator"),
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
it("returns fully populated ctx when all checks pass", async () => {
|
|
367
|
+
mockUserGet.mockResolvedValue({ exists: true, data: () => creatorUserDoc });
|
|
368
|
+
mockProjectGet.mockResolvedValue({
|
|
369
|
+
exists: true,
|
|
370
|
+
data: () => ({
|
|
371
|
+
projectId: "p1",
|
|
372
|
+
ownedBy: { uid: "uid-123" },
|
|
373
|
+
activeUserIds: {},
|
|
374
|
+
activeUsers: [],
|
|
375
|
+
}),
|
|
376
|
+
});
|
|
377
|
+
mockRequireAdmin.mockResolvedValue(undefined);
|
|
378
|
+
const ctx = await assertAuth(makeRequest({ emailVerified: true }), {
|
|
379
|
+
emailVerified: true,
|
|
380
|
+
creator: true,
|
|
381
|
+
projectMembership: { projectId: "p1" },
|
|
382
|
+
admin: { allowJrAdmin: true },
|
|
383
|
+
});
|
|
384
|
+
expect(ctx.uid).toBe("uid-123");
|
|
385
|
+
expect(ctx.userDoc).toMatchObject({ creator: "creator-12345" });
|
|
386
|
+
expect(ctx.project?.isOwner).toBe(true);
|
|
387
|
+
expect(ctx.project?.isMember).toBe(true);
|
|
388
|
+
expect(mockRequireAdmin).toHaveBeenCalled();
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
describe("Parallelization", () => {
|
|
392
|
+
it("calls both user-doc and project-doc get() when both are needed", async () => {
|
|
393
|
+
mockUserGet.mockResolvedValue({ exists: true, data: () => creatorUserDoc });
|
|
394
|
+
mockProjectGet.mockResolvedValue({
|
|
395
|
+
exists: true,
|
|
396
|
+
data: () => memberProjectDoc("uid-123"),
|
|
397
|
+
});
|
|
398
|
+
await assertAuth(makeRequest(), {
|
|
399
|
+
creator: true,
|
|
400
|
+
projectMembership: { projectId: "p1" },
|
|
401
|
+
allowAnyStatus: true,
|
|
402
|
+
});
|
|
403
|
+
expect(mockUserGet).toHaveBeenCalledOnce();
|
|
404
|
+
expect(mockProjectGet).toHaveBeenCalledOnce();
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
//# sourceMappingURL=assertAuth.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assertAuth.test.js","sourceRoot":"","sources":["../../src/server/assertAuth.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,GACX,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,uDAAuD;AAEvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { CallableRequest } from "firebase-functions/v2/https";
|
|
2
|
+
import type { DecodedIdToken } from "firebase-admin/auth";
|
|
3
|
+
import type { Firestore } from "firebase-admin/firestore";
|
|
4
|
+
/**
|
|
5
|
+
* Per-callable requirements passed at every assertAuth call site.
|
|
6
|
+
* These are application-agnostic.
|
|
7
|
+
*/
|
|
8
|
+
export interface AuthRequirements {
|
|
9
|
+
/**
|
|
10
|
+
* Allow unverified email. Default: false.
|
|
11
|
+
* If true, the emailVerified check is skipped entirely.
|
|
12
|
+
* Special-case for system flows (e.g. user registration).
|
|
13
|
+
*/
|
|
14
|
+
allowUnverified?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Require email_verified=true on the auth token.
|
|
17
|
+
* Default: false. Skipped when allowUnverified=true.
|
|
18
|
+
*/
|
|
19
|
+
emailVerified?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Require the user has creator status (config.isUserCreator(user) returns true).
|
|
22
|
+
* Default: false. Implies a Firestore read of the user doc.
|
|
23
|
+
*/
|
|
24
|
+
creator?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Require user status is not 'banned' or 'disabled'.
|
|
27
|
+
* Default: true (always enforced unless allowAnyStatus=true).
|
|
28
|
+
*/
|
|
29
|
+
notBanned?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Skip the not-banned check entirely. Special-case for flows where the
|
|
32
|
+
* user doc may not exist yet (e.g. user registration).
|
|
33
|
+
* Default: false.
|
|
34
|
+
*/
|
|
35
|
+
allowAnyStatus?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Allow banned users to pass the status check. Disabled users still fail.
|
|
38
|
+
* Use ONLY for callables that are a banned user's last recourse
|
|
39
|
+
* (e.g. admin-support chat).
|
|
40
|
+
* Default: false. Does NOT skip the existence check.
|
|
41
|
+
*/
|
|
42
|
+
allowBanned?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Require the caller is a member (or owner) of a given project.
|
|
45
|
+
* Reads the project doc via config.projectPath(projectId).
|
|
46
|
+
*/
|
|
47
|
+
projectMembership?: {
|
|
48
|
+
projectId: string;
|
|
49
|
+
role?: "member" | "owner";
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Require admin status. Delegates to config.requireAdmin().
|
|
53
|
+
* The shape of this object is forwarded verbatim to the consumer's
|
|
54
|
+
* requireAdmin callback — the package does not interpret it.
|
|
55
|
+
*/
|
|
56
|
+
admin?: AdminCheckOptions;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Options forwarded to the consumer's requireAdmin callback. Generic shape.
|
|
60
|
+
* Consuming apps may add their own fields; the package does not validate.
|
|
61
|
+
*/
|
|
62
|
+
export interface AdminCheckOptions {
|
|
63
|
+
allowJrAdmin?: boolean;
|
|
64
|
+
useFastCheck?: boolean;
|
|
65
|
+
errorMessage?: string;
|
|
66
|
+
[key: string]: unknown;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Return value from assertAuth. Generic over the consuming app's user and
|
|
70
|
+
* project document types.
|
|
71
|
+
*/
|
|
72
|
+
export interface AuthContext<TUser = unknown, TProject = unknown> {
|
|
73
|
+
uid: string;
|
|
74
|
+
token: DecodedIdToken;
|
|
75
|
+
/** Populated when the user doc was read (creator OR default not-banned check). */
|
|
76
|
+
userDoc?: TUser;
|
|
77
|
+
/** Populated when projectMembership was checked. */
|
|
78
|
+
project?: {
|
|
79
|
+
data: TProject;
|
|
80
|
+
isOwner: boolean;
|
|
81
|
+
isMember: boolean;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Status the consuming app reports for a user doc. The package only
|
|
86
|
+
* interprets these three values.
|
|
87
|
+
*/
|
|
88
|
+
export type UserStatus = "ok" | "banned" | "disabled";
|
|
89
|
+
/**
|
|
90
|
+
* Configuration for createAssertAuth. The consuming app supplies every
|
|
91
|
+
* TTT-specific (or Q-Sports-specific) operation as a callback.
|
|
92
|
+
*/
|
|
93
|
+
export interface AssertAuthConfig<TUser, TProject> {
|
|
94
|
+
/**
|
|
95
|
+
* Returns the admin Firestore instance. Called every assertAuth invocation
|
|
96
|
+
* that needs a Firestore read. Implementations typically return
|
|
97
|
+
* `admin.firestore()` from a memoized handle.
|
|
98
|
+
*/
|
|
99
|
+
firestore: () => Firestore;
|
|
100
|
+
/**
|
|
101
|
+
* Returns the Firestore document path for a user profile, given a uid.
|
|
102
|
+
* Example (TTT): `userProfiles/${uid}`
|
|
103
|
+
*/
|
|
104
|
+
userProfilePath: (uid: string) => string;
|
|
105
|
+
/**
|
|
106
|
+
* Returns the Firestore document path for a project, given a projectId.
|
|
107
|
+
* Example (TTT): `allProjects/${projectId}`
|
|
108
|
+
*/
|
|
109
|
+
projectPath: (projectId: string) => string;
|
|
110
|
+
/**
|
|
111
|
+
* Returns the user's status given the user doc data. The package only
|
|
112
|
+
* acts on 'ok' | 'banned' | 'disabled'. Apps may store status in any
|
|
113
|
+
* field; this callback interprets it.
|
|
114
|
+
*/
|
|
115
|
+
getUserStatus: (user: TUser) => UserStatus;
|
|
116
|
+
/**
|
|
117
|
+
* Returns true if the user is a creator. Called only when
|
|
118
|
+
* AuthRequirements.creator === true.
|
|
119
|
+
*/
|
|
120
|
+
isUserCreator: (user: TUser) => boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Returns true if the given uid is the owner of the project.
|
|
123
|
+
*/
|
|
124
|
+
isProjectOwner: (project: TProject, uid: string) => boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Returns true if the given uid is a member of the project. Owners are
|
|
127
|
+
* typically also members — the callback should reflect that.
|
|
128
|
+
*/
|
|
129
|
+
isProjectMember: (project: TProject, uid: string) => boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Admin check. Called only when AuthRequirements.admin is set. The
|
|
132
|
+
* package forwards uid, token, and the admin options verbatim. The
|
|
133
|
+
* callback throws (typically HttpsError) on failure.
|
|
134
|
+
*/
|
|
135
|
+
requireAdmin: (uid: string, token: DecodedIdToken, options: AdminCheckOptions) => Promise<void>;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The function returned by createAssertAuth.
|
|
139
|
+
*/
|
|
140
|
+
export type AssertAuthFn<TUser, TProject> = (request: CallableRequest, requirements?: AuthRequirements) => Promise<AuthContext<TUser, TProject>>;
|
|
141
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,iBAAiB,CAAC,EAAE;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;KAC3B,CAAC;IAEF;;;;OAIG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,KAAK,GAAG,OAAO,EAAE,QAAQ,GAAG,OAAO;IAC9D,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,cAAc,CAAC;IACtB,kFAAkF;IAClF,OAAO,CAAC,EAAE,KAAK,CAAC;IAChB,oDAAoD;IACpD,OAAO,CAAC,EAAE;QACR,IAAI,EAAE,QAAQ,CAAC;QACf,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,EAAE,OAAO,CAAC;KACnB,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEtD;;;GAGG;AACH,MAAM,WAAW,gBAAgB,CAAC,KAAK,EAAE,QAAQ;IAC/C;;;;OAIG;IACH,SAAS,EAAE,MAAM,SAAS,CAAC;IAE3B;;;OAGG;IACH,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAEzC;;;OAGG;IACH,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAE3C;;;;OAIG;IACH,aAAa,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,UAAU,CAAC;IAE3C;;;OAGG;IACH,aAAa,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC;IAExC;;OAEG;IACH,cAAc,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAE5D;;;OAGG;IACH,eAAe,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAE7D;;;;OAIG;IACH,YAAY,EAAE,CACZ,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,iBAAiB,KACvB,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,CAAC,KAAK,EAAE,QAAQ,IAAI,CAC1C,OAAO,EAAE,eAAe,EACxB,YAAY,CAAC,EAAE,gBAAgB,KAC5B,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// packages/auth-core/src/server/types.ts
|
|
2
|
+
//
|
|
3
|
+
// Types for the createAssertAuth factory. Fully generic over the consuming
|
|
4
|
+
// app's user and project document shapes. The package contains no TTT- or
|
|
5
|
+
// Q-Sports-specific knowledge.
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,2EAA2E;AAC3E,0EAA0E;AAC1E,+BAA+B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttt-productions/auth-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Shared Firebase authentication utilities and React hooks for TTT Productions apps",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"./react": {
|
|
24
24
|
"types": "./dist/react/index.d.ts",
|
|
25
25
|
"default": "./dist/react/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./server": {
|
|
28
|
+
"types": "./dist/server/index.d.ts",
|
|
29
|
+
"default": "./dist/server/index.js"
|
|
26
30
|
}
|
|
27
31
|
},
|
|
28
32
|
"scripts": {
|
|
@@ -33,10 +37,21 @@
|
|
|
33
37
|
},
|
|
34
38
|
"peerDependencies": {
|
|
35
39
|
"firebase": ">=10.0.0",
|
|
40
|
+
"firebase-admin": ">=10.0.0",
|
|
41
|
+
"firebase-functions": ">=4.0.0",
|
|
36
42
|
"react": ">=19.0.0",
|
|
37
43
|
"react-dom": ">=19.0.0"
|
|
38
44
|
},
|
|
39
45
|
"peerDependenciesMeta": {
|
|
46
|
+
"firebase": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"firebase-admin": {
|
|
50
|
+
"optional": true
|
|
51
|
+
},
|
|
52
|
+
"firebase-functions": {
|
|
53
|
+
"optional": true
|
|
54
|
+
},
|
|
40
55
|
"react": {
|
|
41
56
|
"optional": true
|
|
42
57
|
},
|
|
@@ -48,6 +63,8 @@
|
|
|
48
63
|
"@types/react": "^19.0.0",
|
|
49
64
|
"@types/react-dom": "^19.0.0",
|
|
50
65
|
"firebase": "^10.0.0",
|
|
66
|
+
"firebase-admin": "^12.0.0",
|
|
67
|
+
"firebase-functions": "^7.0.0",
|
|
51
68
|
"react": "^19.2.0",
|
|
52
69
|
"react-dom": "^19.2.0",
|
|
53
70
|
"typescript": "^5.8.3"
|