Assign name on login, change confirm modals, change push warning, reject reqs before connect

This commit is contained in:
artur
2024-02-07 10:41:00 +03:00
parent 326d824451
commit d00e16139e
8 changed files with 146 additions and 61 deletions

View File

@@ -1,7 +1,7 @@
import { useModalSearchParams } from '@/hooks/useModalSearchParams'
import { Modal } from '@/shared/Modal/Modal'
import { MODAL_PARAMS_KEYS } from '@/types/modal'
import { call, getShortenNpub } from '@/utils/helpers/helpers'
import { call, getAppIconTitle, getShortenNpub } from '@/utils/helpers/helpers'
import { Avatar, Box, Stack, Typography } from '@mui/material'
import { useParams, useSearchParams } from 'react-router-dom'
import { useAppSelector } from '@/store/hooks/redux'
@@ -29,19 +29,20 @@ export const ModalConfirmConnect = () => {
const triggerApp = apps.find((app) => app.appNpub === appNpub)
const { name, icon = '' } = triggerApp || {}
const appName = name || getShortenNpub(appNpub)
const appAvatarTitle = getAppIconTitle(name, appNpub)
const handleActionTypeChange = (_: any, value: ACTION_TYPE | null) => {
if (!value) return undefined
return setSelectedActionType(value)
}
const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.CONFIRM_CONNECT, {
onClose: async (sp) => {
sp.delete('appNpub')
sp.delete('reqId')
await swicCall('confirm', pendingReqId, false, false)
},
})
// const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.CONFIRM_CONNECT, {
// onClose: async (sp) => {
// sp.delete('appNpub')
// sp.delete('reqId')
// await swicCall('confirm', pendingReqId, false, false)
// },
// })
const closeModalAfterRequest = createHandleCloseReplace(MODAL_PARAMS_KEYS.CONFIRM_CONNECT, {
onClose: (sp) => {
sp.delete('appNpub')
@@ -79,23 +80,27 @@ export const ModalConfirmConnect = () => {
}
return (
<Modal open={isModalOpened} withCloseButton={!isPopup} onClose={!isPopup ? handleCloseModal : undefined}>
<Modal title='Connection request' open={isModalOpened} withCloseButton={false}
// withCloseButton={!isPopup} onClose={!isPopup ? handleCloseModal : undefined}
>
<Stack gap={'1rem'} paddingTop={'1rem'}>
<Stack direction={'row'} gap={'1rem'} alignItems={'center'} marginBottom={'1rem'}>
<Avatar
variant="square"
variant="rounded"
sx={{
width: 56,
height: 56,
}}
src={icon}
/>
>
{appAvatarTitle}
</Avatar>
<Box>
<Typography variant="h5" fontWeight={600}>
{appName}
</Typography>
<Typography variant="body2" color={'GrayText'}>
Would like to connect to your account
New app would like to connect
</Typography>
</Box>
</Stack>
@@ -103,7 +108,7 @@ export const ModalConfirmConnect = () => {
<ActionToggleButton
value={ACTION_TYPE.BASIC}
title="Basic permissions"
description="Read your public key, sign notes and reactions"
description="Read your public key, sign notes, reactions, zaps, etc"
// hasinfo
/>
{/* <ActionToggleButton
@@ -115,7 +120,7 @@ export const ModalConfirmConnect = () => {
<ActionToggleButton
value={ACTION_TYPE.CUSTOM}
title="On demand"
description="Assign permissions when the app asks for them"
description="Confirm permissions when the app asks for them"
/>
</StyledToggleButtonsGroup>
<Stack direction={'row'} gap={'1rem'}>

View File

@@ -1,7 +1,7 @@
import { useModalSearchParams } from '@/hooks/useModalSearchParams'
import { Modal } from '@/shared/Modal/Modal'
import { MODAL_PARAMS_KEYS } from '@/types/modal'
import { call, getShortenNpub, getSignReqKind } from '@/utils/helpers/helpers'
import { call, getAppIconTitle, getShortenNpub, getSignReqKind } from '@/utils/helpers/helpers'
import { Avatar, Box, List, ListItem, ListItemIcon, ListItemText, Stack, Typography } from '@mui/material'
import { useParams, useSearchParams } from 'react-router-dom'
import { useAppSelector } from '@/store/hooks/redux'
@@ -57,6 +57,7 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({ confirmEventReqs
const triggerApp = apps.find((app) => app.appNpub === appNpub)
const { name, icon = '' } = triggerApp || {}
const appName = name || getShortenNpub(appNpub)
const appAvatarTitle = getAppIconTitle(name, appNpub)
const handleActionTypeChange = (_: any, value: ACTION_TYPE | null) => {
if (!value) return undefined
@@ -118,7 +119,9 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({ confirmEventReqs
}
return (
<Modal open={isModalOpened} withCloseButton={!isPopup} onClose={!isPopup ? handleCloseModal : undefined}>
<Modal title='Permission request' open={isModalOpened} withCloseButton={false}
// withCloseButton={!isPopup} onClose={!isPopup ? handleCloseModal : undefined}
>
<Stack gap={'1rem'} paddingTop={'1rem'}>
<Stack direction={'row'} gap={'1rem'} alignItems={'center'} marginBottom={'1rem'}>
<Avatar
@@ -129,13 +132,15 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({ confirmEventReqs
borderRadius: '12px',
}}
src={icon}
/>
>
{appAvatarTitle}
</Avatar>
<Box>
<Typography variant="h5" fontWeight={600}>
{appName}
</Typography>
<Typography variant="body2" color={'GrayText'}>
Would like your permission to
App wants to perform these actions
</Typography>
</Box>
</Stack>

View File

@@ -1,19 +1,27 @@
import React, { FC, ReactNode } from 'react'
import { FC, ReactNode } from 'react'
import { IconContainer, StyledContainer } from './styled'
import { BoxProps, Typography } from '@mui/material'
import { BoxProps, Stack, Typography } from '@mui/material'
type WarningProps = {
message: string | ReactNode
Icon?: ReactNode
message?: string | ReactNode
hint?: string | ReactNode
icon?: ReactNode
} & BoxProps
export const Warning: FC<WarningProps> = ({ message, Icon, ...restProps }) => {
export const Warning: FC<WarningProps> = ({ hint, message, icon, ...restProps }) => {
return (
<StyledContainer {...restProps}>
{Icon && <IconContainer>{Icon}</IconContainer>}
<Typography flex={1} noWrap>
{icon && <IconContainer>{icon}</IconContainer>}
<Stack flex={1} direction={'column'} gap={'0.2rem'}>
<Typography noWrap>
{message}
</Typography>
{hint && (
<Typography>
{hint}
</Typography>
)}
</Stack>
</StyledContainer>
)
}

View File

@@ -16,7 +16,7 @@ export const IconContainer = styled((props: BoxProps) => <Box {...props} />)(()
width: '40px',
height: '40px',
borderRadius: '50%',
background: 'blue',
background: 'grey',
display: 'grid',
placeItems: 'center',
}))

View File

@@ -8,9 +8,9 @@ import NDK, {
NDKPrivateKeySigner,
NDKSigner,
} from '@nostr-dev-kit/ndk'
import { NOAUTHD_URL, WEB_PUSH_PUBKEY, NIP46_RELAYS, MIN_POW, MAX_POW, KIND_RPC } from '../utils/consts'
import { NOAUTHD_URL, WEB_PUSH_PUBKEY, NIP46_RELAYS, MIN_POW, MAX_POW, KIND_RPC, DOMAIN } from '../utils/consts'
import { Nip04 } from './nip04'
import { getReqPerm, getShortenNpub, isPackagePerm } from '@/utils/helpers/helpers'
import { fetchNip05, getReqPerm, getShortenNpub, isPackagePerm } from '@/utils/helpers/helpers'
import { NostrPowEvent, minePow } from './pow'
//import { PrivateKeySigner } from './signer'
@@ -225,7 +225,7 @@ export class NoauthBackend {
public setNotifCallback(cb: () => void) {
if (this.notifCallback) {
this.notify()
// this.notify()
}
this.notifCallback = cb
}
@@ -246,6 +246,13 @@ export class NoauthBackend {
return Buffer.from(await this.swg.crypto.subtle.digest('SHA-256', Buffer.from(s))).toString('hex')
}
private async fetchNpubName(npub: string) {
const url = `${NOAUTHD_URL}/name?npub=${npub}`
const r = await fetch(url)
const d = await r.json()
return d?.names?.length ? d.names[0] as string : ''
}
private async sendPost({ url, method, headers, body }: { url: string; method: string; headers: any; body: string }) {
const r = await fetch(url, {
method,
@@ -559,6 +566,12 @@ export class NoauthBackend {
}
const appNpub = nip19.npubEncode(remotePubkey)
const connected = !!this.apps.find(a => a.appNpub === appNpub)
if (!connected && method !== 'connect') {
console.log('ignoring request before connect', method, id, appNpub, npub)
return false
}
const req: DbPending = {
id,
npub,
@@ -574,12 +587,13 @@ export class NoauthBackend {
const onAllow = async (manual: boolean, allow: boolean, remember: boolean, options?: any) => {
// confirm
console.log(Date.now(), allow ? 'allowed' : 'disallowed', npub, method, options, params)
if (manual) {
await dbi.confirmPending(id, allow)
if (!(method === 'connect' && !allow)) {
// only add app if it's not 'disallow connect'
if (!(await dbi.getApp(req.appNpub))) {
// add app on 'allow connect'
if (method === 'connect' && allow) {
// if (!(await dbi.getApp(req.appNpub))) {
await dbi.addApp({
appNpub: req.appNpub,
npub: req.npub,
@@ -592,7 +606,6 @@ export class NoauthBackend {
// reload
self.apps = await dbi.listApps()
}
}
} else {
// just send to db w/o waiting for it
dbi.addConfirmed({
@@ -612,7 +625,8 @@ export class NoauthBackend {
let newPerms = [getReqPerm(req)]
if (allow && options && options.perms) newPerms = options.perms
for (const p of newPerms)
// write new perms confirmed by user
for (const p of newPerms) {
await dbi.addPerm({
id: req.id,
npub: req.npub,
@@ -621,13 +635,17 @@ export class NoauthBackend {
value: allow ? '1' : '0',
timestamp: Date.now(),
})
}
// reload
this.perms = await dbi.listPerms()
// confirm pending requests that might now have
// the proper perms
const otherReqs = self.confirmBuffer.filter((r) => r.req.appNpub === req.appNpub)
console.log('updated perms', this.perms, 'otherReqs', otherReqs)
console.log('updated perms', this.perms, 'otherReqs', otherReqs, 'connected', connected)
for (const r of otherReqs) {
const perm = this.getPerm(r.req)
let perm = this.getPerm(r.req)
if (perm) {
r.cb(perm === '1', false)
}
@@ -675,7 +693,7 @@ export class NoauthBackend {
backend.rpc.sendResponse(id, remotePubkey, 'auth_url', KIND_RPC, authUrl)
// show notifs
this.notify()
// this.notify()
// notify main thread to ask for user concent
this.updateUI()
@@ -793,7 +811,7 @@ export class NoauthBackend {
await this.sendKeyToServer(npub, enckey, pwh)
}
private async fetchKey(npub: string, passphrase: string, name: string) {
private async fetchKey(npub: string, passphrase: string, nip05: string) {
const { type, data: pubkey } = nip19.decode(npub)
if (type !== 'npub') throw new Error(`Invalid npub ${npub}`)
const { pwh } = await this.keysModule.generatePassKey(pubkey, passphrase)
@@ -803,13 +821,50 @@ export class NoauthBackend {
const key = this.enckeys.find((k) => k.npub === npub)
if (key) return this.keyInfo(key)
let name = ''
let existingName = true
// check name - user might have provided external nip05,
// or just his npub - we must fetch their name from our
// server, and if not exists - try to assign one
const npubName = await this.fetchNpubName(npub)
if (npubName) {
// already have name for this npub
console.log("existing npub name", npub, npubName)
name = npubName
} else if (nip05.includes('@')) {
// no name for them?
const [nip05name, domain] = nip05.split('@')
if (domain === DOMAIN) {
// wtf? how did we learn their npub if
// it's the name on our server but we can't fetch it?
console.log("existing name", nip05name)
name = nip05name
} else {
// try to take same name on our domain
existingName = false
name = nip05name
let takenName = await fetchNip05(`${name}@${DOMAIN}`)
if (takenName) {
// already taken? try name_domain as name
name = `${nip05name}_${domain}`
takenName = await fetchNip05(`${name}@${DOMAIN}`)
}
if (takenName) {
console.log("All names taken, leave without a name?")
name = ''
}
}
}
console.log("fetch", { name, existingName })
// add new key
const nsec = await this.keysModule.decryptKeyPass({
pubkey,
enckey,
passphrase,
})
const k = await this.addKey({ name, nsec, existingName: true })
const k = await this.addKey({ name, nsec, existingName })
this.updateUI()
return k
}

View File

@@ -1,7 +1,7 @@
import React, { FC } from 'react'
import { FC } from 'react'
import { Warning } from '@/components/Warning/Warning'
import { CircularProgress, Stack } from '@mui/material'
import GppMaybeIcon from '@mui/icons-material/GppMaybe'
import { CircularProgress, Stack, Typography } from '@mui/material'
import AutoModeOutlinedIcon from '@mui/icons-material/AutoModeOutlined'
type BackgroundSigningWarningProps = {
isEnabling: boolean
@@ -13,10 +13,16 @@ export const BackgroundSigningWarning: FC<BackgroundSigningWarningProps> = ({ is
<Warning
message={
<Stack direction={'row'} alignItems={'center'} gap={'1rem'}>
Please enable push notifications {isEnabling ? <CircularProgress size={'1.5rem'} /> : null}
Enable background service {isEnabling ? <CircularProgress size={'1.5rem'} /> : null}
</Stack>
}
Icon={<GppMaybeIcon htmlColor="white" />}
hint={
<Typography variant='body2'>
Please allow notifications
for background operation.
</Typography>
}
icon={<AutoModeOutlinedIcon htmlColor="white" />}
onClick={isEnabling ? undefined : onEnableBackSigning}
/>
)

View File

@@ -20,7 +20,7 @@ export const useBackgroundSigning = () => {
await askNotificationPermission()
const result = await swicCall('enablePush')
if (!result) throw new Error('Failed to activate the push subscription')
notify('Push notifications enabled!', 'success')
notify('Background service enabled!', 'success')
setShowWarning(false)
} catch (error: any) {
notify(`Failed to enable push subscription: ${error}`, 'error')

View File

@@ -15,6 +15,12 @@ export const getShortenNpub = (npub = '') => {
return npub.substring(0, 10) + '...' + npub.slice(-4)
}
export const getAppIconTitle = (name: string | undefined, appNpub: string) => {
return name
? name[0].toLocaleUpperCase()
: appNpub.substring(4, 7);
}
export const getProfileUsername = (profile: MetaEvent | null, npub: string) => {
return profile?.info?.name || profile?.info?.display_name || getShortenNpub(npub)
}