diff --git a/common/user.ts b/common/user.ts index 8713717d..b93ed4d6 100644 --- a/common/user.ts +++ b/common/user.ts @@ -35,4 +35,5 @@ export type PrivateUser = { unsubscribedFromGenericEmails?: boolean initialDeviceToken?: string initialIpAddress?: string + apiKey?: string } diff --git a/firestore.rules b/firestore.rules index 24ab0941..feba35d9 100644 --- a/firestore.rules +++ b/firestore.rules @@ -21,6 +21,9 @@ service cloud.firestore { match /private-users/{userId} { allow read: if resource.data.id == request.auth.uid || isAdmin(); + allow update: if (resource.data.id == request.auth.uid || isAdmin()) + && request.resource.data.diff(resource.data).affectedKeys() + .hasOnly(['apiKey']); } match /private-users/{userId}/views/{viewId} { diff --git a/web/lib/firebase/users.ts b/web/lib/firebase/users.ts index 6867b2d0..d7acd2bb 100644 --- a/web/lib/firebase/users.ts +++ b/web/lib/firebase/users.ts @@ -55,6 +55,13 @@ export async function updateUser(userId: string, update: Partial) { await updateDoc(doc(db, 'users', userId), { ...update }) } +export async function updatePrivateUser( + userId: string, + update: Partial +) { + await updateDoc(doc(db, 'private-users', userId), { ...update }) +} + export function listenForUser( userId: string, setUser: (user: User | null) => void diff --git a/web/pages/profile.tsx b/web/pages/profile.tsx index 07930d49..7a920270 100644 --- a/web/pages/profile.tsx +++ b/web/pages/profile.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react' +import { RefreshIcon } from '@heroicons/react/outline' import Router from 'next/router' import { AddFundsButton } from 'web/components/add-funds-button' @@ -13,7 +14,7 @@ import { uploadImage } from 'web/lib/firebase/storage' import { Col } from 'web/components/layout/col' import { Row } from 'web/components/layout/row' import { User } from 'common/user' -import { updateUser } from 'web/lib/firebase/users' +import { updateUser, updatePrivateUser } from 'web/lib/firebase/users' import { defaultBannerUrl } from 'web/components/user-page' import { SiteLink } from 'web/components/site-link' import Textarea from 'react-expanding-textarea' @@ -63,6 +64,7 @@ export default function ProfilePage() { const [avatarLoading, setAvatarLoading] = useState(false) const [name, setName] = useState(user?.name || '') const [username, setUsername] = useState(user?.username || '') + const [apiKey, setApiKey] = useState(privateUser?.apiKey || '') useEffect(() => { if (user) { @@ -72,6 +74,12 @@ export default function ProfilePage() { } }, [user]) + useEffect(() => { + if (privateUser) { + setApiKey(privateUser.apiKey || '') + } + }, [privateUser]) + const updateDisplayName = async () => { const newName = cleanDisplayName(name) @@ -103,6 +111,17 @@ export default function ProfilePage() { } } + const updateApiKey = async (e: React.MouseEvent) => { + const newApiKey = crypto.randomUUID() + if (user?.id != null) { + setApiKey(newApiKey) + await updatePrivateUser(user.id, { apiKey: newApiKey }).catch(() => { + setApiKey(privateUser?.apiKey || '') + }) + } + e.preventDefault() + } + const fileHandler = async (event: any) => { const file = event.target.files[0] @@ -155,7 +174,6 @@ export default function ProfilePage() {
- -
+ +
+ +
+ + +
+