Skip to content

Commit

Permalink
Service Account Setup for onSchedule & blocking functions (#162)
Browse files Browse the repository at this point in the history
# Service Account Setup for onSchedule & blocking functions

## ♻️ Current situation & Problem
In [the last staging
deployment](https://github.com/StanfordBDHG/ENGAGE-HF-Firebase/actions/runs/11468013868/job/31973299652),
the CI failed due to missing permissions on the account in use. I
checked and found out that they are not yet connected to the
serviceAccount. This PR adds them to the service account, but the
service account may also require additional permissions on top of this
change.

## ⚙️ Release Notes 
*Add a bullet point list summary of the feature and possible migration
guides if this is a breaking change so this section can be added to the
release notes.*
*Include code snippets that provide examples of the feature implemented
or links to the documentation if it appends or changes the public
interface.*


## 📚 Documentation
*Please ensure that you properly document any additions in conformance
to [Spezi Documentation
Guide](https://github.com/StanfordSpezi/.github/blob/main/DOCUMENTATIONGUIDE.md).*
*You can use this section to describe your solution, but we encourage
contributors to document your reasoning and changes using in-line
documentation.*


## ✅ Testing
*Please ensure that the PR meets the testing requirements set by CodeCov
and that new functionality is appropriately tested.*
*This section describes important information about the tests and why
some elements might not be testable.*


### Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/StanfordBDHG/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordBDHG/.github/blob/main/CONTRIBUTING.md):
- [ ] I agree to follow the [Code of
Conduct](https://github.com/StanfordBDHG/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordBDHG/.github/blob/main/CONTRIBUTING.md).
  • Loading branch information
pauljohanneskraft authored Oct 24, 2024
1 parent 936a433 commit ab4115f
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 45 deletions.
96 changes: 53 additions & 43 deletions functions/src/functions/blocking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,58 +12,68 @@ import {
beforeUserCreated,
beforeUserSignedIn,
} from 'firebase-functions/v2/identity'
import { serviceAccount } from './helpers.js'
import { getServiceFactory } from '../services/factory/getServiceFactory.js'

export const beforeUserCreatedFunction = beforeUserCreated(async (event) => {
const userId = event.data.uid
export const beforeUserCreatedFunction = beforeUserCreated(
{ serviceAccount: serviceAccount },
async (event) => {
const userId = event.data.uid

const factory = getServiceFactory()
const userService = factory.user()
const credential = event.credential
const factory = getServiceFactory()
const userService = factory.user()
const credential = event.credential

// Escape hatch for users using invitation code to enroll
if (!credential) return
// Escape hatch for users using invitation code to enroll
if (!credential) return

if (event.data.email === undefined)
throw new https.HttpsError(
'invalid-argument',
'Email address is required for user.',
)
if (event.data.email === undefined)
throw new https.HttpsError(
'invalid-argument',
'Email address is required for user.',
)

const organization = await userService.getOrganizationBySsoProviderId(
credential.providerId,
)
const organization = await userService.getOrganizationBySsoProviderId(
credential.providerId,
)

if (organization === undefined)
throw new https.HttpsError('failed-precondition', 'Organization not found.')
if (organization === undefined)
throw new https.HttpsError(
'failed-precondition',
'Organization not found.',
)

const invitation = await userService.getInvitationByCode(event.data.email)
if (invitation?.content === undefined) {
throw new https.HttpsError(
'not-found',
'No valid invitation code found for user.',
)
}
const invitation = await userService.getInvitationByCode(event.data.email)
if (invitation?.content === undefined) {
throw new https.HttpsError(
'not-found',
'No valid invitation code found for user.',
)
}

if (
invitation.content.user.type === UserType.admin &&
invitation.content.user.organization !== organization.id
)
throw new https.HttpsError(
'failed-precondition',
'Organization does not match invitation code.',
if (
invitation.content.user.type === UserType.admin &&
invitation.content.user.organization !== organization.id
)
throw new https.HttpsError(
'failed-precondition',
'Organization does not match invitation code.',
)

await userService.enrollUser(invitation, userId)
await factory.trigger().userEnrolled(userId)
})
await userService.enrollUser(invitation, userId)
await factory.trigger().userEnrolled(userId)
},
)

export const beforeUserSignedInFunction = beforeUserSignedIn(async (event) => {
try {
const userService = getServiceFactory().user()
await userService.updateClaims(event.data.uid)
logger.info(`beforeUserSignedIn finished successfully.`)
} catch (error) {
logger.error(`beforeUserSignedIn finished with error: ${String(error)}`)
}
})
export const beforeUserSignedInFunction = beforeUserSignedIn(
{ serviceAccount: serviceAccount },
async (event) => {
try {
const userService = getServiceFactory().user()
await userService.updateClaims(event.data.uid)
logger.info(`beforeUserSignedIn finished successfully.`)
} catch (error) {
logger.error(`beforeUserSignedIn finished with error: ${String(error)}`)
}
},
)
6 changes: 4 additions & 2 deletions functions/src/functions/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ import {
} from 'firebase-functions/v2/https'
import { z } from 'zod'

export const serviceAccount = `cloudfunctionsserviceaccount@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`

export function validatedOnCall<Schema extends z.ZodTypeAny, Return>(
name: string,
schema: Schema,
handler: (request: CallableRequest<z.output<Schema>>) => Return,
options: CallableOptions = {
invoker: 'public',
serviceAccount: `cloudfunctionsserviceaccount@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`,
serviceAccount: serviceAccount,
},
): CallableFunction<
z.input<Schema>,
Expand Down Expand Up @@ -63,7 +65,7 @@ export function validatedOnRequest<Schema extends z.ZodTypeAny>(
) => void | Promise<void>,
options: https.HttpsOptions = {
invoker: 'public',
serviceAccount: `cloudfunctionsserviceaccount@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`,
serviceAccount: serviceAccount,
},
): https.HttpsFunction {
return onRequest(options, async (request, response) => {
Expand Down
4 changes: 4 additions & 0 deletions functions/src/functions/onSchedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
//

import { onSchedule } from 'firebase-functions/v2/scheduler'
import { serviceAccount } from './helpers.js'
import { getServiceFactory } from '../services/factory/getServiceFactory.js'

export const onScheduleEveryMorning = onSchedule(
{
schedule: '0 8 * * *',
timeZone: 'America/Los_Angeles',
serviceAccount: serviceAccount,
},
async () => getServiceFactory().trigger().everyMorning(),
)
Expand All @@ -21,6 +23,7 @@ export const onScheduleEvery15Minutes = onSchedule(
{
schedule: '*/15 * * * *',
timeZone: 'America/Los_Angeles',
serviceAccount: serviceAccount,
},
async () => getServiceFactory().trigger().every15Minutes(),
)
Expand All @@ -29,6 +32,7 @@ export const onScheduleUpdateMedicationRecommendations = onSchedule(
{
schedule: '0 0 * * *',
timeZone: 'America/Los_Angeles',
serviceAccount: serviceAccount,
},
async () =>
getServiceFactory().trigger().updateRecommendationsForAllPatients(),
Expand Down

0 comments on commit ab4115f

Please sign in to comment.