Assign name on login, change confirm modals, change push warning, reject reqs before connect
This commit is contained in:
@@ -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'}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
}))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user