diff --git a/functions/src/auth2Users.ts b/functions/src/auth2Users.ts index 95a17a4..af876bd 100755 --- a/functions/src/auth2Users.ts +++ b/functions/src/auth2Users.ts @@ -8,11 +8,16 @@ import got from 'got' const moment = require('moment') const gravatar = require('gravatar') + +export interface Auth2UsersOptions { + syncGravatar: boolean +} + const Auth2Users = (admin: Admin.app.App) => { const firestore = admin.firestore() const auth = admin.auth() - const listAllUsers = async (nextPageToken?: string) => { + const listAllUsers = async (options: Auth2UsersOptions, nextPageToken?: string) => { // List batch of users, 1000 at a time. const listUsersResult = await auth.listUsers(1000, nextPageToken) await each(listUsersResult.users, async (userRecord: UserRecord) => { @@ -24,18 +29,7 @@ const Auth2Users = (admin: Admin.app.App) => { displayName, metadata: { creationTime, lastSignInTime }, } = userRecord - const gravatarUrl = gravatar.url(email, { - protocol: 'https', - default: '404' - }) - let hasGravatar = false - try { - await got.get(gravatarUrl) - info('found gravatar.', {gravatarUrl}) - hasGravatar = true - } catch (err) { - info('Error while fetching gravatar.', {gravatarUrl, error: err}) - } + const createdAt = moment(creationTime) .utc() @@ -50,20 +44,36 @@ const Auth2Users = (admin: Admin.app.App) => { email: email || '', emailVerified, displayName: displayName || '', - lastSignedInAt, - gravatarUrl: hasGravatar ? gravatarUrl : null + lastSignedInAt + } + + if (options.syncGravatar) { + let hasGravatar = false + const gravatarUrl = gravatar.url(email, { + protocol: 'https', + default: '404' + }) + try { + await got.get(gravatarUrl) + info('found gravatar.', { gravatarUrl }) + hasGravatar = true + } catch (err) { + info('Error while fetching gravatar.', { gravatarUrl, error: err }) + } + data.gravatarUrl = hasGravatar ? gravatarUrl : null } + await userRef.set(data, { merge: true }) info(`Updated ${uid} ${createdAt} ${lastSignedInAt}`) } catch (err) { - error('Error while syncing auth2user.', {'uid': userRecord.uid, 'error': err}); + error('Error while syncing auth2user.', { 'uid': userRecord.uid, 'error': err }); } }) info( `checking listUsersResult.pageToken: ${listUsersResult.pageToken} num of results previously found: ${listUsersResult.users.length}`) if (listUsersResult.pageToken) { // List next batch of users. - await listAllUsers(listUsersResult.pageToken) + await listAllUsers(options, listUsersResult.pageToken) } else { info('Done. Exiting...') return diff --git a/functions/src/auth2UsersCMD.ts b/functions/src/auth2UsersCMD.ts index f0086c8..9af5a60 100755 --- a/functions/src/auth2UsersCMD.ts +++ b/functions/src/auth2UsersCMD.ts @@ -11,7 +11,7 @@ const admin: Admin.app.App = Admin.initializeApp({ }) const auth2Users = Auth2Users(admin) -auth2Users() +auth2Users({ syncGravatar: true }) .then((res) => { console.info('done', res) return diff --git a/functions/src/index.ts b/functions/src/index.ts index 540fb15..d34a24d 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -1,5 +1,5 @@ import AddContact from './addContact' -import Auth2Users from './auth2Users' +import Auth2Users, { Auth2UsersOptions } from './auth2Users' import Contacts2MailChimp from './contacts2MailChimp' import DeleteUser from './deleteUser' import GenerateICal from './generateICal' @@ -11,17 +11,17 @@ import UpdateEvents from './updateEvents' import Users2Contacts from './users2Contacts' import * as functions from 'firebase-functions' import * as Admin from 'firebase-admin' -import {EMAIL} from './fields' -import {props} from 'bluebird' +import { EMAIL } from './fields' +import { props } from 'bluebird' const admin: Admin.app.App = Admin.initializeApp() const firestore = admin.firestore() const apiKey = functions.config().mailchimp.apikey -const {app_id, city_id} = functions.config().openweathermap +const { app_id, city_id } = functions.config().openweathermap const { membership_fee_in_cents, - secret_keys: {live, test} + secret_keys: { live, test } } = functions.config().stripe const addContactImpl = AddContact(admin) @@ -34,16 +34,16 @@ const purgeUsersUnder13 = PurgeUsersUnder13(admin, apiKey, false) const sendMembershipReminders = SendMembershipReminders(admin) const stripeImpl = Stripe(admin, { membershipFeeInCents: membership_fee_in_cents, - secretKeys: {live, test} + secretKeys: { live, test } }) const users2Contacts = Users2Contacts(admin) const updateEvents = UpdateEvents(admin, app_id, city_id) const AUTH_2_USERS_TIMEOUT_IN_SECONDS = 180 -const auth2UsersExec = async () => { +const auth2UsersExec = (options: Auth2UsersOptions) => async () => { try { - await auth2Users() + await auth2Users(options) console.info('Calling process.exit(0)') setTimeout(function () { process.exit(0) @@ -58,127 +58,129 @@ const auth2UsersExec = async () => { } export const purgeUsersUnder13CronJob = functions.pubsub - .schedule('10 */6 * * *') - .onRun(async () => await purgeUsersUnder13()) -export const auth2UsersCronJob = functions.pubsub - .runWith({timeoutSeconds: AUTH_2_USERS_TIMEOUT_IN_SECONDS}) - .schedule('20 */6 * * *') - .onRun(async () => await auth2UsersExec) -export const auth2UsersOnCreate = functions.auth - .runWith({timeoutSeconds: AUTH_2_USERS_TIMEOUT_IN_SECONDS}) - .user().onCreate(auth2UsersExec) + .schedule('10 */6 * * *') + .onRun(async () => await purgeUsersUnder13()) +export const auth2UsersCronJob = functions + .runWith({ timeoutSeconds: AUTH_2_USERS_TIMEOUT_IN_SECONDS }) + .pubsub + .schedule('20 */6 * * *') + .onRun(async () => await auth2UsersExec({ syncGravatar: true })) +export const auth2UsersOnCreate = functions + .runWith({ timeoutSeconds: AUTH_2_USERS_TIMEOUT_IN_SECONDS }) + .auth + .user().onCreate(auth2UsersExec({ syncGravatar: false })) export const users2ContactsCronJob = functions.pubsub - .schedule('30 */6 * * *') - .onRun(async () => { - try { - await users2Contacts() - console.info('users2ContactsCronJob: done') - } catch (err) { - console.error('users2ContactsCronJob: error:', err) - } - }) + .schedule('30 */6 * * *') + .onRun(async () => { + try { + await users2Contacts() + console.info('users2ContactsCronJob: done') + } catch (err) { + console.error('users2ContactsCronJob: error:', err) + } + }) export const contacts2MailChimpCronJob = functions - .runWith({timeoutSeconds: 180}) - .pubsub.schedule('40 */6 * * *') - .onRun(async () => { - try { - await contacts2MailChimp() - console.info('Calling process.exit(0)') - setTimeout(function () { - process.exit(0) - }, 5000) - } catch (err) { - console.error(err) - console.info('Calling process.exit(1)') - setTimeout(function () { - process.exit(1) - }, 5000) - } - }) + .runWith({ timeoutSeconds: 180 }) + .pubsub.schedule('40 */6 * * *') + .onRun(async () => { + try { + await contacts2MailChimp() + console.info('Calling process.exit(0)') + setTimeout(function () { + process.exit(0) + }, 5000) + } catch (err) { + console.error(err) + console.info('Calling process.exit(1)') + setTimeout(function () { + process.exit(1) + }, 5000) + } + }) export const updateEventsCronJob = functions.pubsub - .schedule('*/20 * * * *') - .onRun(async () => await updateEvents()) + .schedule('*/20 * * * *') + .onRun(async () => await updateEvents()) export const waiver = functions - .https.onRequest(async (req: functions.https.Request, res: functions.Response) => { - res.redirect('https://docs.google.com/forms/d/e/1FAIpQLSfYxlbWAzK1jAcdE_5-ijxORNVz2YU4BdSVt2Dk-DByncIEkw/viewform') - }) + .https.onRequest(async (req: functions.https.Request, res: functions.Response) => { + res.redirect('https://docs.google.com/forms/d/e/1FAIpQLSfYxlbWAzK1jAcdE_5-ijxORNVz2YU4BdSVt2Dk-DByncIEkw/viewform') + }) export const ical = functions - .runWith({memory: '512MB'}) - .https.onRequest(async (req: functions.https.Request, res: functions.Response) => { - try { - const body = await generateICal() - res.set({ - 'cache-control': 'no-cache, no-store, max-age=0, must-revalidate', - // 'content-security-policy': "script-src 'report-sample' 'nonce-b3O76BbGW8VYkTGK5Nxtvw' 'unsafe-inline' 'strict-dynamic' https: http: 'unsafe-eval';object-src 'none';base-uri 'self';report-uri /calendar/cspreport", - 'content-type': 'text/calendar; charset=UTF-8', - // 'date': 'Sat, 15 Jun 2019 22:23:46 GMT', - expires: 'Mon, 01 Jan 1990 00:00:00 GMT', - pragma: 'no-cache', - // 'server': 'GSE', - 'strict-transport-security': - 'max-age=31536000; includeSubDomains; preload', - 'x-content-type-options': 'nosniff', - 'x-frame-options': 'SAMEORIGIN', - 'x-xss-protection': '1; mode=block' - }) - res.send(Buffer.from(body)) - } catch (err) { - console.error('ical generation got an err:', err) - res.status(500).send('Internal Server Error') - } - }) - -export const stripe = functions.runWith({memory: '512MB'}).https.onCall(stripeImpl) + .runWith({ memory: '512MB' }) + .https.onRequest(async (req: functions.https.Request, res: functions.Response) => { + try { + const body = await generateICal() + res.set({ + 'cache-control': 'no-cache, no-store, max-age=0, must-revalidate', + // 'content-security-policy': "script-src 'report-sample' 'nonce-b3O76BbGW8VYkTGK5Nxtvw' 'unsafe-inline' 'strict-dynamic' https: http: 'unsafe-eval';object-src 'none';base-uri 'self';report-uri /calendar/cspreport", + 'content-type': 'text/calendar; charset=UTF-8', + // 'date': 'Sat, 15 Jun 2019 22:23:46 GMT', + expires: 'Mon, 01 Jan 1990 00:00:00 GMT', + pragma: 'no-cache', + // 'server': 'GSE', + 'strict-transport-security': + 'max-age=31536000; includeSubDomains; preload', + 'x-content-type-options': 'nosniff', + 'x-frame-options': 'SAMEORIGIN', + 'x-xss-protection': '1; mode=block' + }) + res.send(Buffer.from(body)) + } catch (err) { + console.error('ical generation got an err:', err) + res.status(500).send('Internal Server Error') + } + }) + +export const stripe = functions.runWith({ memory: '512MB' }).https.onCall(stripeImpl) export const addContact = functions - .runWith({memory: '512MB'}) - .https.onCall(addContactImpl) + .runWith({ memory: '512MB' }) + .https.onCall(addContactImpl) export const getMembers = functions - .runWith({timeoutSeconds: 30, memory: '512MB'}) - .https.onCall(getMembersImpl) + .runWith({ timeoutSeconds: 30, memory: '512MB' }) + .https.onCall(getMembersImpl) export const deleteUser = functions - .runWith({timeoutSeconds: 30, memory: '512MB'}) - .https.onCall(async (data, context) => { - if (!context || !context.auth || !context.auth.uid) { + .runWith({ timeoutSeconds: 30, memory: '512MB' }) + .https.onCall(async (data, context) => { + if (!context || !context.auth || !context.auth.uid) { + throw new functions.https.HttpsError( + 'unauthenticated', + 'unauthenticated.' + ) + } + const currentUID = context.auth.uid + const targetUID = data.uid + let targetEmail + if (targetUID !== currentUID) { + const { docUsersDelete, docUser } = await props({ + docUsersDelete: firestore.doc('permissions/usersDelete').get(), + docUser: firestore.doc(`users/${targetUID}`).get() + }) + const usersDelete = docUsersDelete.data() + const allowDelete = usersDelete && usersDelete[currentUID] + if (!allowDelete) { throw new functions.https.HttpsError( - 'unauthenticated', - 'unauthenticated.' + 'permission-denied', + 'permission-denied.' ) } - const currentUID = context.auth.uid - const targetUID = data.uid - let targetEmail - if (targetUID !== currentUID) { - const {docUsersDelete, docUser} = await props({ - docUsersDelete: firestore.doc('permissions/usersDelete').get(), - docUser: firestore.doc(`users/${targetUID}`).get() - }) - const usersDelete = docUsersDelete.data() - const allowDelete = usersDelete && usersDelete[currentUID] - if (!allowDelete) { - throw new functions.https.HttpsError( - 'permission-denied', - 'permission-denied.' - ) - } - // @ts-ignore - targetEmail = docUser.data() && docUser.data()[EMAIL] - if (!targetEmail) { - throw new functions.https.HttpsError('not-found', 'not-found.') - } - } else { - targetEmail = context.auth.token[EMAIL] + // @ts-ignore + targetEmail = docUser.data() && docUser.data()[EMAIL] + if (!targetEmail) { + throw new functions.https.HttpsError('not-found', 'not-found.') } - await deleteUserImpl({uid: targetUID, email: targetEmail}) - }) + } else { + targetEmail = context.auth.token[EMAIL] + } + await deleteUserImpl({ uid: targetUID, email: targetEmail }) + }) export const sendMembershipRemindersCronJob = functions.pubsub - .schedule('0 19 * * *') - .onRun(async () => await sendMembershipReminders()) + .schedule('0 19 * * *') + .onRun(async () => await sendMembershipReminders())