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

Feat/937 admin email #943

Merged
merged 2 commits into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions functions/src/admin/getUserEmail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as functions from 'firebase-functions'
import { auth } from 'firebase-admin'

/**
* For requests coming from authenticated admins, request the email
* for another user (not stored in DB so pulled from auth system)
*/
export const getUserEmail = functions.https.onCall(async (data, context) => {
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called ' + 'while authenticated.',
)
}
const { uid } = data
// TODO - add server-side auth to check request coming from admin
// (does still check clientside)
try {
const { email } = await auth().getUser(uid)
return email
} catch (error) {
console.error(error)
throw new functions.https.HttpsError('not-found', JSON.stringify(error))
}
})
6 changes: 6 additions & 0 deletions functions/src/admin/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
Admin functions are designed to be called from the frontend by
administrators
*/

export * from './getUserEmail'
4 changes: 4 additions & 0 deletions functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as IntegrationsSlack from './Integrations/firebase-slack'
import * as IntegrationsDiscord from './Integrations/firebase-discord'
import { FirebaseUserBackup } from './Integrations/firebase-userBackup'
import * as IntegrationsEmail from './Integrations/firebase-email'
import * as Admin from './admin'

// the following endpoints are exposed for use by various triggers
// see individual files for more informaiton
Expand All @@ -22,3 +23,6 @@ exports.notifyHowToAccepted = IntegrationsDiscord.notifyHowToAccepted
exports.notifyEventAccepted = IntegrationsDiscord.notifyEventAccepted
exports.firebaseUserBackup = FirebaseUserBackup
exports.emailNotificationDemo = IntegrationsEmail.notifyEmailDemo
// CC Note, 2020-04-40
// folder-based naming conventions should be encourage from now on
exports.adminGetUserEmail = Admin.getUserEmail
38 changes: 38 additions & 0 deletions src/components/AdminContact/AdminContact.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as React from 'react'
import { inject, observer } from 'mobx-react'
import { Button } from 'src/components/Button'
import { AdminStore } from 'src/stores/Admin/admin.store'
import { IUser } from 'src/models/user.models'

/*
Button to request a user's email from the firebase auth database and open in default mail client
*/

interface IProps {
user: IUser
adminStore?: AdminStore
}
interface IState {
disabled: boolean
}
@inject('adminStore')
@observer
export class AdminContact extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props)
this.state = { disabled: false }
}
getUserEmail = async () => {
this.setState({ disabled: true })
const email = await this.props.adminStore!.getUserEmail(this.props.user)
this.setState({ disabled: false })
window.location.href = `mailto:${email}`
}
public render() {
return (
<Button disabled={this.state.disabled} onClick={this.getUserEmail}>
Email
</Button>
)
}
}
7 changes: 7 additions & 0 deletions src/pages/User/content/UserPage/UserPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ import PPLogo from 'src/assets/images/precious-plastic-logo-official.svg'
import { IUploadedFileMeta } from 'src/stores/storage'
import { IConvertedFileMeta } from 'src/components/ImageInput/ImageInput'
import { Loader } from 'src/components/Loader'
import { AuthWrapper } from 'src/components/Auth/AuthWrapper'
import { AdminContact } from 'src/components/AdminContact/AdminContact'

interface IRouterCustomParams {
id: string
Expand Down Expand Up @@ -550,6 +552,11 @@ export class UserPage extends React.Component<
{this.renderLinks(user.links)}
</UserContactInfo>
)}
<AuthWrapper roleRequired={'admin'}>
<Box mt={3}>
<AdminContact user={user} />
</Box>
</AuthWrapper>
</Box>
<Box
width={['100%', '100%', '20%']}
Expand Down
18 changes: 18 additions & 0 deletions src/stores/Admin/admin.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RootStore } from '..'
import { action, observable } from 'mobx'
import { IUser, UserRole } from 'src/models/user.models'
import { ITag } from 'src/models/tags.model'
import { functions } from 'src/utils/firebase'

/*********************************************************************************
* The admin store contains methods for updating user permissions.
Expand Down Expand Up @@ -56,6 +57,23 @@ export class AdminStore extends ModuleStore {
}
}

/**
* Make call to backend function to retrieve user email from auth provider
* (as it is not stored in the database)
*/
public async getUserEmail(user: IUser) {
try {
const res = await functions.httpsCallable('adminGetUserEmail')({
uid: user._authID,
})
const email = res.data
return email
} catch (error) {
console.error(error)
throw new Error(`unable to get user email - ${error.message}`)
}
}

private async _getUsersByRole(role: UserRole) {
return this.db
.collection<IUser>('v3_users')
Expand Down