Skip to content

Commit

Permalink
Can set firebase admin custom claim and use it in the frontend app
Browse files Browse the repository at this point in the history
  • Loading branch information
hernan-clich committed Jan 16, 2022
1 parent a101fde commit 8fcb488
Show file tree
Hide file tree
Showing 8 changed files with 891 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ yarn-error.log*

# vercel
.vercel

# firebase
serviceAccount.json
20 changes: 20 additions & 0 deletions app/config/firebase/admin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as admin from 'firebase-admin';
import type { ServiceAccount } from 'firebase-admin';
import * as serviceAccount from './serviceAccount.json';

const initializeAdminApp = () => {
if (!admin.apps.length) {
try {
admin.initializeApp({
credential: admin.credential.cert(serviceAccount as ServiceAccount)
});
} catch {
// eslint-disable-next-line no-console
console.error('Firebase admin initialization error');
}
}
};

initializeAdminApp();

export const auth = admin.auth();
12 changes: 11 additions & 1 deletion app/hooks/useAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,23 @@ import { TUser } from '~types/user';

interface IAuthContext {
isAuthenticated: boolean;
isAdmin: boolean;
logout: () => void;
signInWithGoogle: (shouldRedirectAfterSucces?: boolean) => void;
user: User | null;
}

const AuthContext = createContext<IAuthContext>({
isAuthenticated: false,
isAdmin: false,
logout: () => null,
signInWithGoogle: () => null,
user: null
});

export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<User | null>(null);
const [isAdmin, setIsAdmin] = useState<boolean>(false);
const router = useRouter();
const { addDbDocumentWithCustomId, getDbDocument } = useDbCrud(EDbCollections.users);

Expand All @@ -45,6 +48,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {

if (!dbUser) {
addDbDocumentWithCustomId(signIn.user.uid, {
email: signIn.user.email as string,
imageUrl: signIn.user.photoURL as string,
joinedSince: new Date().toISOString(),
name: signIn.user.displayName as string,
Expand Down Expand Up @@ -73,11 +77,15 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
const cancelAuthListener = onIdTokenChanged(auth, async (user) => {
if (user) {
const token = await user.getIdToken();
const idTokenResult = await user?.getIdTokenResult();

setTokenCookie(token);
setUser(user);
setIsAdmin(Boolean(idTokenResult?.claims?.isAdmin));
} else {
removeTokenCookie();
setUser(null);
setIsAdmin(false);
}
});

Expand All @@ -87,7 +95,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
}, []);

return (
<AuthContext.Provider value={{ user, isAuthenticated: !!user, logout, signInWithGoogle }}>
<AuthContext.Provider
value={{ user, isAdmin, isAuthenticated: !!user, logout, signInWithGoogle }}
>
{children}
</AuthContext.Provider>
);
Expand Down
23 changes: 23 additions & 0 deletions app/screens/Admin/Users/components/AdminUsersContent/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import CustomTable from '~components/CustomTable';
import CustomText from '~components/CustomText';
import { useAuth } from '~hooks/useAuth';
import useDbSnapshot from '~hooks/useDbSnapshot';
import { TUser } from '~types/user';
import * as Styled from './styles';
Expand Down Expand Up @@ -30,13 +31,35 @@ function AdminUsersContent() {
imageUrl,
textFields: [id, name, joinedSince]
}));
const { user } = useAuth();

// @todo: This obviously doesn't belong here, make admins in the proper place
const handleClick = async (req: { email: string }) => {
const response = await fetch('/api/set-admin-role', {
method: 'POST',
body: JSON.stringify(req)
});
const data = await response.json();
return data;
};

return (
<Styled.AdminUsersContentContainer>
<div className="headingContainer">
<CustomText size="regular" weight="bold">
Usuarios
</CustomText>
{/* @todo: Remove this stuff */}
<button
type="button"
onClick={() =>
handleClick({
email: user?.email as string
})
}
>
Make admin
</button>
</div>
<CustomTable columnHeaders={COLUMN_HEADERS} tableContent={tableContent} />
</Styled.AdminUsersContentContainer>
Expand Down
1 change: 1 addition & 0 deletions app/types/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type TUser = {
email: string;
id: string;
imageUrl: string;
name: string;
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
"dependencies": {
"clsx": "^1.1.1",
"firebase": "^9.5.0",
"firebase-admin": "^10.0.1",
"js-cookie": "^3.0.1",
"next": "11.0.1",
"next-connect": "^0.11.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-hook-form": "^7.20.2",
Expand Down
16 changes: 16 additions & 0 deletions pages/api/set-admin-role.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import nc from 'next-connect';
import { auth } from '~config/firebase/admin';

export default nc().post(async (req: NextApiRequest, res: NextApiResponse) => {
try {
const parsedBody = JSON.parse(req.body);
const user = await auth.getUserByEmail(parsedBody.email);

await auth.setCustomUserClaims(user.uid, { isAdmin: true });

res.status(200).json({ message: `User ${user.email} has been made an admin` });
} catch (e) {
res.status(500).json({ error: e });
}
});
Loading

0 comments on commit 8fcb488

Please sign in to comment.