Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I148 create admin role using firebase functions and restriction rules to firestore security rules #166

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
75d2457
labeled the functions
NicholasChoong Dec 6, 2022
c707468
testing roles
NicholasChoong Dec 6, 2022
a1df0d3
add a link to a comment
NicholasChoong Dec 6, 2022
e20971d
named export all functions as functions
NicholasChoong Dec 7, 2022
5f12c1d
created auth onCreate trigger function
NicholasChoong Dec 7, 2022
81926a3
add revoke time doc after token revocation
NicholasChoong Dec 7, 2022
01d33dd
added valid token check and roles checks
NicholasChoong Dec 7, 2022
ff2be0c
added temp function to create admins
NicholasChoong Dec 7, 2022
12c7c4f
changed boolean to string
NicholasChoong Dec 8, 2022
88969cb
Merge branch 'main' of https://github.com/codersforcauses/poops into …
NicholasChoong Dec 21, 2022
cbd1f27
Merge branch 'main' of https://github.com/codersforcauses/poops into …
NicholasChoong Dec 21, 2022
9636eec
init next Api to replace firebase functions temp
NicholasChoong Dec 29, 2022
8d4b30f
ignore firebase admin config
NicholasChoong Dec 29, 2022
0c45bcb
removed baseUrl as it clashed with node imports
NicholasChoong Dec 29, 2022
802f541
created admin page to test next api
NicholasChoong Dec 29, 2022
310fdb9
Merge branch 'main' of https://github.com/codersforcauses/poops into …
NicholasChoong Dec 29, 2022
118dd43
added rules to restrict access
NicholasChoong Dec 29, 2022
61bfb64
minor fixes
NicholasChoong Dec 29, 2022
1b3b240
refactored security rules
NicholasChoong Dec 29, 2022
ebc4602
added admin nav button
NicholasChoong Dec 29, 2022
82199c4
added admin to auth context
NicholasChoong Dec 29, 2022
1eaa1f8
moved API keys to .env
NicholasChoong Dec 29, 2022
278a1f6
refactored to use context
NicholasChoong Dec 29, 2022
56c0dbf
standardised setRole func
NicholasChoong Dec 29, 2022
06b7636
added mod me button in home page
NicholasChoong Dec 29, 2022
19a2b0a
debug on vercel
NicholasChoong Dec 29, 2022
330013e
debug on vercel
NicholasChoong Dec 29, 2022
57e9f70
removed console
NicholasChoong Dec 29, 2022
a91f7e6
append claims instead of overwriting with new one
NicholasChoong Jan 4, 2023
077c34b
removed useless file
NicholasChoong Jan 4, 2023
d033f3e
removed rules for possible future features
NicholasChoong Jan 4, 2023
8d15cda
removed useless firebase functions
NicholasChoong Jan 4, 2023
0279385
removed type declaration as types can be infered
NicholasChoong Jan 4, 2023
1d61aa4
removed type declaration as types can be infered
NicholasChoong Jan 4, 2023
1e2338f
Merge branch 'i148-create_admin_role_using_firebase_functions_and_res…
NicholasChoong Jan 4, 2023
09084d3
minor refactor
NicholasChoong Jan 4, 2023
9496fed
removed useless function
NicholasChoong Jan 4, 2023
2ba6202
fixed ref issue for Links
NicholasChoong Jan 4, 2023
15ea2d2
now connects to emulator for firebase admin
NicholasChoong Jan 4, 2023
bf1c44b
disabled auto unused import removal & console warn
NicholasChoong Jan 4, 2023
ea9a3ab
added more types and doc name is uid
NicholasChoong Jan 4, 2023
6af2956
minor code readability improvement
NicholasChoong Jan 4, 2023
5b92064
get role doc by uid
NicholasChoong Jan 4, 2023
f879663
updated rules due to doc name changes
NicholasChoong Jan 4, 2023
5b1203c
renamed getUserToken to refreshUserToken
NicholasChoong Jan 4, 2023
e6d80d9
Merge branch 'main' of https://github.com/codersforcauses/poops into …
NicholasChoong Jan 4, 2023
8930345
Merge branch 'main' of https://github.com/codersforcauses/poops into …
NicholasChoong Jan 4, 2023
de255eb
Merge branch 'i148-create_admin_role_using_firebase_functions_and_res…
NicholasChoong Jan 4, 2023
9038c73
refactored func to arrow func and async/await
NicholasChoong Jan 4, 2023
19f47f2
refactored func to arrow func and async/await
NicholasChoong Jan 4, 2023
2f03cb9
Merge branch 'i148-create_admin_role_using_firebase_functions_and_res…
NicholasChoong Jan 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = {
],
rules: {
'no-unused-vars': 'off',
'no-console': 'warn',
'no-console': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',

'react/jsx-curly-brace-presence': [
Expand All @@ -25,7 +25,7 @@ module.exports = {

//#region //*=========== Unused Import ===========
'@typescript-eslint/no-unused-vars': 'off',
'unused-imports/no-unused-imports': 'warn',
'unused-imports/no-unused-imports': 'off',
'unused-imports/no-unused-vars': [
'warn',
{
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ sitemap-*.xml
**/public/worker-*.js
**/public/sw.js.map
**/public/workbox-*.js.map
**/public/worker-*.js.map
**/public/worker-*.js.map
31 changes: 22 additions & 9 deletions firebase/firestore.rules
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId}/{document=**} {
function isSignIn(){
function isSignIn(){
return request.auth != null;
}

function isOwner(){
return request.auth.uid == userId;
}
// function hasVerifiedEmail(){
// return request.auth.token.email_verified;
// }

function hasVerifiedEmail(){
return request.auth.token.email_verified;
}
function isOwner(uid){
return request.auth.uid == uid;
}

function isAdmin(){
return request.auth.token.admin;
}

match /users/{userId}/{document=**} {

allow read, write: if isSignIn() && isOwner(userId);
}

allow read, write: if isSignIn() && isOwner();
match /roles/{userId} {
// function checkEmail(){
// return request.auth.token.email == email;
// }
allow read: if isSignIn() && isAdmin() && isOwner(userId);
allow write: if false;
}
}
}
23 changes: 23 additions & 0 deletions firebase/functions/callable/createAdmin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { functions, auth, firestore } from '../main'

export const createAdmin = functions
.region('australia-southeast1')
scratchclaggy marked this conversation as resolved.
Show resolved Hide resolved
.https.onRequest(async (req, res) => {
const userId = '<USER_ID>'
const role = 'admin'

const claims: Record<string, boolean> = {}
claims[role] = true

await auth.setCustomUserClaims(userId, claims)

const userRecord = await auth.getUser(userId)
const data = { role: 'admin', by: 'none' }
await firestore
.collection('roles')
.doc(userRecord.email || userId)
.set(data)

res.setHeader('Content-Type', 'application/json')
res.send(JSON.stringify({ status: 'success' }))
})
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { PubSub } from '@google-cloud/pubsub'
import { region } from './main'
import { functions } from '../main'

const pubsub = new PubSub()

export const pubsubWriter = region('australia-southeast1').https.onRequest(
async (req, res) => {
export const pubsubWriter = functions
.region('australia-southeast1')
.https.onRequest(async (req, res) => {
console.log('Pubsub Emulator:', process.env.PUBSUB_EMULATOR_HOST)

const msg = await pubsub.topic('billing').publishJSON(
Expand All @@ -23,5 +24,4 @@ export const pubsubWriter = region('australia-southeast1').https.onRequest(
res.json({
published: msg
})
}
)
})
13 changes: 0 additions & 13 deletions firebase/functions/helloWorld.ts

This file was deleted.

9 changes: 4 additions & 5 deletions firebase/functions/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export * from './main'
export * from './helloWorld'
export * from './pubsubWriter'
export * from './billingKillswitch'
export * from './visitTrigger'
export * from './callable/pubsubWriter'
export * from './trigger/billingKillswitch'
export * from './trigger/visitTrigger'
export * from './callable/createAdmin'
6 changes: 5 additions & 1 deletion firebase/functions/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { config } from 'firebase-functions/v1'
import { initializeApp } from 'firebase-admin/app'
import { getAuth } from 'firebase-admin/auth'
import { getFirestore } from 'firebase-admin/firestore'

initializeApp(config().firebase)

export * from 'firebase-functions/v1'
export * as functions from 'firebase-functions/v1'
export const auth = getAuth()
export const firestore = getFirestore()
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CloudBillingClient } from '@google-cloud/billing'
import { region } from './main'
import { functions } from '../main'

const billing = new CloudBillingClient()

Expand Down Expand Up @@ -42,7 +42,8 @@ const _disableBillingForProject = async (projectName: string) => {
return `Billing disabled: ${JSON.stringify(res)}`
}

export const stopBilling = region('australia-southeast1')
export const stopBilling = functions
.region('australia-southeast1')
.pubsub.topic('billing')
.onPublish(async (pubsubEvent) => {
const pubsubData = JSON.parse(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { firestore } from './main'
import { getFirestore, DocumentData } from 'firebase-admin/firestore'
const db = getFirestore()
import { functions, firestore } from '../main'
import { DocumentData } from 'firebase-admin/firestore'

interface UserStat {
numVisits: number
numHours: number
Expand All @@ -13,7 +13,7 @@ interface UserStat {
* Automatically updates the user stats when a visit is added,
* deleted or updated.
*/
export const updateVisitTrigger = firestore
export const updateVisitTrigger = functions.firestore
.document('users/{userId}/visits/{visitId}')
.onWrite(async (change, context) => {
const userId = context.params.userId
Expand All @@ -26,7 +26,7 @@ export const updateVisitTrigger = firestore
}

// require oldStats to append to newStats
const userRef = db.collection('users').doc(userId)
const userRef = firestore.collection('users').doc(userId)
const userDoc = await userRef.get()
const oldStats: UserStat = userDoc.data()?.stats

Expand Down
2 changes: 1 addition & 1 deletion firebase/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@google-cloud/billing": "^3.1.3",
"@google-cloud/pubsub": "^3.2.1",
"firebase-admin": "^11.3.0",
"firebase-functions": "^4.1.0"
"firebase-functions": "^4.1.1"
},
"devDependencies": {
"@types/node": "^16.18.3",
Expand Down
8 changes: 4 additions & 4 deletions firebase/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1326,10 +1326,10 @@ firebase-functions-test@^3.0.0:
lodash "^4.17.5"
ts-deepmerge "^2.0.1"

firebase-functions@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/firebase-functions/-/firebase-functions-4.1.0.tgz#4d8780a2c0aee6f362b3a632af293911427ec597"
integrity sha512-brbww5lGQVm8+d4KFmHF+O8wJBthws1NGXgphy7UDguMbUoW0fq6bL0NI442w+3nDE8IYUbnR4p3U8/cLAhnOA==
firebase-functions@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/firebase-functions/-/firebase-functions-4.1.1.tgz#47c39a40d6cdaad9a46cb7ee3b9ffbb9bcaa7f2c"
integrity sha512-D0fhHO7m3OfZp5TpbO+ClsEo6vmr8uaR4kt7sePhQgcF1OCRI6YT5dEa9szaQekGKoao/YeZ+C5HVxEGsLxD9Q==
dependencies:
"@types/cors" "^2.8.5"
"@types/express" "4.17.3"
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"class-variance-authority": "0.3.0",
"firebase": "9.14.0",
"jotai": "^1.12.0",
"firebase-admin": "^11.4.1",
"next": "12.3.4",
"next-pwa": "5.6.0",
"react": "18.2.0",
Expand Down
14 changes: 14 additions & 0 deletions src/components/TopNav/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import Image from 'next/image'
import Link from 'next/link'

import Button from '@/components/UI/button'
import { useAuth } from '@/context/Firebase/Auth/context'

function TopNav() {
const { isAdmin } = useAuth()
return (
<div className='h-16 w-full'>
<nav
Expand All @@ -9,6 +14,15 @@ function TopNav() {
>
<div className='flex justify-between'>
<Image alt='logo' src='/images/logo.png' width={100} height={64} />
{isAdmin && (
<Link href='admin/'>
<a className='flex justify-center'>
<Button size='small' intent='secondary'>
Admin Page
</Button>
</a>
</Link>
)}
</div>
<hr className='mb-3 h-0.5 border-primary-dark bg-primary-dark text-primary-dark' />
</nav>
Expand Down
6 changes: 5 additions & 1 deletion src/context/Firebase/Auth/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ export interface FirebaseContextProps {
externalAuthSignIn?: (auth: Auth, provider: AuthProvider) => void
logOut?: () => void
currentUser: User | null
isAdmin: boolean
refreshUserToken: () => Promise<void>
}
//set auth and current user as a context api to be called by other funcs
export const AuthContext = createContext<FirebaseContextProps>({
auth: auth,
currentUser: null
currentUser: null,
isAdmin: false,
refreshUserToken: async () => undefined
})

export const useAuth = () => useContext(AuthContext)