Privileged app passwords (#4200)
* add checkbox to create privileged app password * add indicator to privileged app pwds to list * bump api * oops missed the yarnlock * adjust modal padding * lowercase * one more lowercase --------- Co-authored-by: Hailey <me@haileyok.com>zio/stable
parent
406993cf0e
commit
d2c42cf169
|
@ -49,7 +49,7 @@
|
||||||
"open-analyzer": "EXPO_PUBLIC_OPEN_ANALYZER=1 yarn build-web"
|
"open-analyzer": "EXPO_PUBLIC_OPEN_ANALYZER=1 yarn build-web"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "^0.12.11",
|
"@atproto/api": "^0.12.13",
|
||||||
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
||||||
|
|
|
@ -25,12 +25,13 @@ export function useAppPasswordCreateMutation() {
|
||||||
return useMutation<
|
return useMutation<
|
||||||
ComAtprotoServerCreateAppPassword.OutputSchema,
|
ComAtprotoServerCreateAppPassword.OutputSchema,
|
||||||
Error,
|
Error,
|
||||||
{name: string}
|
{name: string; privileged: boolean}
|
||||||
>({
|
>({
|
||||||
mutationFn: async ({name}) => {
|
mutationFn: async ({name, privileged}) => {
|
||||||
return (
|
return (
|
||||||
await getAgent().com.atproto.server.createAppPassword({
|
await getAgent().com.atproto.server.createAppPassword({
|
||||||
name,
|
name,
|
||||||
|
privileged,
|
||||||
})
|
})
|
||||||
).data
|
).data
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,20 +8,23 @@ import {
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
|
import {s} from '#/lib/styles'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
import {isNative} from '#/platform/detection'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {
|
import {
|
||||||
useAppPasswordCreateMutation,
|
useAppPasswordCreateMutation,
|
||||||
useAppPasswordsQuery,
|
useAppPasswordsQuery,
|
||||||
} from '#/state/queries/app-passwords'
|
} from '#/state/queries/app-passwords'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {Button} from '#/view/com/util/forms/Button'
|
||||||
import {s} from 'lib/styles'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import {isNative} from 'platform/detection'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {Button} from '../util/forms/Button'
|
import {atoms as a} from '#/alf'
|
||||||
import {Text} from '../util/text/Text'
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
import * as Toast from '../util/Toast'
|
import {KeyboardPadding} from '#/components/KeyboardPadding'
|
||||||
|
|
||||||
export const snapPoints = ['70%']
|
export const snapPoints = ['90%']
|
||||||
|
|
||||||
const shadesOfBlue: string[] = [
|
const shadesOfBlue: string[] = [
|
||||||
'AliceBlue',
|
'AliceBlue',
|
||||||
|
@ -70,6 +73,7 @@ export function Component({}: {}) {
|
||||||
)
|
)
|
||||||
const [appPassword, setAppPassword] = useState<string>()
|
const [appPassword, setAppPassword] = useState<string>()
|
||||||
const [wasCopied, setWasCopied] = useState(false)
|
const [wasCopied, setWasCopied] = useState(false)
|
||||||
|
const [privileged, setPrivileged] = useState(false)
|
||||||
|
|
||||||
const onCopy = React.useCallback(() => {
|
const onCopy = React.useCallback(() => {
|
||||||
if (appPassword) {
|
if (appPassword) {
|
||||||
|
@ -109,7 +113,7 @@ export function Component({}: {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newPassword = await mutateAppPassword({name})
|
const newPassword = await mutateAppPassword({name, privileged})
|
||||||
if (newPassword) {
|
if (newPassword) {
|
||||||
setAppPassword(newPassword.password)
|
setAppPassword(newPassword.password)
|
||||||
} else {
|
} else {
|
||||||
|
@ -140,25 +144,15 @@ export function Component({}: {}) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, pal.view]} testID="addAppPasswordsModal">
|
<View style={[styles.container, pal.view]} testID="addAppPasswordsModal">
|
||||||
<View>
|
|
||||||
{!appPassword ? (
|
{!appPassword ? (
|
||||||
|
<>
|
||||||
|
<View>
|
||||||
<Text type="lg" style={[pal.text]}>
|
<Text type="lg" style={[pal.text]}>
|
||||||
<Trans>
|
<Trans>
|
||||||
Please enter a unique name for this App Password or use our
|
Please enter a unique name for this App Password or use our
|
||||||
randomly generated one.
|
randomly generated one.
|
||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
|
||||||
<Text type="lg" style={[pal.text]}>
|
|
||||||
<Text type="lg-bold" style={[pal.text, s.mr5]}>
|
|
||||||
<Trans>Here is your app password.</Trans>
|
|
||||||
</Text>
|
|
||||||
<Trans>
|
|
||||||
Use this to sign into the other app along with your handle.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
{!appPassword ? (
|
|
||||||
<View style={[pal.btn, styles.textInputWrapper]}>
|
<View style={[pal.btn, styles.textInputWrapper]}>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, pal.text]}
|
style={[styles.input, pal.text]}
|
||||||
|
@ -181,7 +175,38 @@ export function Component({}: {}) {
|
||||||
accessibilityHint={_(msg`Input name for app password`)}
|
accessibilityHint={_(msg`Input name for app password`)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
|
<Text type="xs" style={[pal.textLight, s.mb10, s.mt2]}>
|
||||||
|
<Trans>
|
||||||
|
Can only contain letters, numbers, spaces, dashes, and
|
||||||
|
underscores. Must be at least 4 characters long, but no more than
|
||||||
|
32 characters long.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
<Toggle.Item
|
||||||
|
type="checkbox"
|
||||||
|
label={_(msg`Allow access to your direct messages`)}
|
||||||
|
value={privileged}
|
||||||
|
onChange={val => setPrivileged(val)}
|
||||||
|
name="privileged"
|
||||||
|
style={a.my_md}>
|
||||||
|
<Toggle.Checkbox />
|
||||||
|
<Toggle.LabelText>
|
||||||
|
<Trans>Allow access to your direct messages</Trans>
|
||||||
|
</Toggle.LabelText>
|
||||||
|
</Toggle.Item>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
|
<View>
|
||||||
|
<Text type="lg" style={[pal.text]}>
|
||||||
|
<Text type="lg-bold" style={[pal.text, s.mr5]}>
|
||||||
|
<Trans>Here is your app password.</Trans>
|
||||||
|
</Text>
|
||||||
|
<Trans>
|
||||||
|
Use this to sign into the other app along with your handle.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[pal.border, styles.passwordContainer, pal.btn]}
|
style={[pal.border, styles.passwordContainer, pal.btn]}
|
||||||
onPress={onCopy}
|
onPress={onCopy}
|
||||||
|
@ -203,23 +228,14 @@ export function Component({}: {}) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
{appPassword ? (
|
|
||||||
<Text type="lg" style={[pal.textLight, s.mb10]}>
|
<Text type="lg" style={[pal.textLight, s.mb10]}>
|
||||||
<Trans>
|
<Trans>
|
||||||
For security reasons, you won't be able to view this again. If you
|
For security reasons, you won't be able to view this again. If you
|
||||||
lose this password, you'll need to generate a new one.
|
lose this password, you'll need to generate a new one.
|
||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
</>
|
||||||
<Text type="xs" style={[pal.textLight, s.mb10, s.mt2]}>
|
|
||||||
<Trans>
|
|
||||||
Can only contain letters, numbers, spaces, dashes, and underscores.
|
|
||||||
Must be at least 4 characters long, but no more than 32 characters
|
|
||||||
long.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
)}
|
)}
|
||||||
<View style={styles.btnContainer}>
|
<View style={styles.btnContainer}>
|
||||||
<Button
|
<Button
|
||||||
|
@ -230,6 +246,7 @@ export function Component({}: {}) {
|
||||||
onPress={!appPassword ? createAppPassword : onDone}
|
onPress={!appPassword ? createAppPassword : onDone}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
<KeyboardPadding />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,32 +5,34 @@ import {
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {ScrollView} from 'react-native-gesture-handler'
|
import {ScrollView} from 'react-native-gesture-handler'
|
||||||
import {Text} from '../com/util/text/Text'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {Button} from '../com/util/forms/Button'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import * as Toast from '../com/util/Toast'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
|
||||||
import {CommonNavigatorParams} from 'lib/routes/types'
|
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
|
||||||
import {useFocusEffect} from '@react-navigation/native'
|
|
||||||
import {ViewHeader} from '../com/util/ViewHeader'
|
|
||||||
import {CenteredView} from 'view/com/util/Views'
|
|
||||||
import {Trans, msg} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useFocusEffect} from '@react-navigation/native'
|
||||||
|
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||||
|
|
||||||
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
|
import {CommonNavigatorParams} from '#/lib/routes/types'
|
||||||
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {useLanguagePrefs} from '#/state/preferences'
|
import {useLanguagePrefs} from '#/state/preferences'
|
||||||
import {
|
import {
|
||||||
useAppPasswordsQuery,
|
|
||||||
useAppPasswordDeleteMutation,
|
useAppPasswordDeleteMutation,
|
||||||
|
useAppPasswordsQuery,
|
||||||
} from '#/state/queries/app-passwords'
|
} from '#/state/queries/app-passwords'
|
||||||
import {ErrorScreen} from '../com/util/error/ErrorScreen'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import {Button} from '#/view/com/util/forms/Button'
|
||||||
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
|
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||||
|
import {CenteredView} from 'view/com/util/Views'
|
||||||
|
import {atoms as a} from '#/alf'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
|
import * as Prompt from '#/components/Prompt'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppPasswords'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppPasswords'>
|
||||||
export function AppPasswords({}: Props) {
|
export function AppPasswords({}: Props) {
|
||||||
|
@ -135,6 +137,7 @@ export function AppPasswords({}: Props) {
|
||||||
testID={`appPassword-${i}`}
|
testID={`appPassword-${i}`}
|
||||||
name={password.name}
|
name={password.name}
|
||||||
createdAt={password.createdAt}
|
createdAt={password.createdAt}
|
||||||
|
privileged={password.privileged}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{isTabletOrDesktop && (
|
{isTabletOrDesktop && (
|
||||||
|
@ -207,10 +210,12 @@ function AppPassword({
|
||||||
testID,
|
testID,
|
||||||
name,
|
name,
|
||||||
createdAt,
|
createdAt,
|
||||||
|
privileged,
|
||||||
}: {
|
}: {
|
||||||
testID: string
|
testID: string
|
||||||
name: string
|
name: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
|
privileged?: boolean
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
@ -255,6 +260,18 @@ function AppPassword({
|
||||||
}).format(new Date(createdAt))}
|
}).format(new Date(createdAt))}
|
||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
|
{privileged && (
|
||||||
|
<View style={[a.flex_row, a.gap_sm, a.align_center, a.mt_xs]}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="circle-exclamation"
|
||||||
|
color={pal.colors.textLight}
|
||||||
|
size={14}
|
||||||
|
/>
|
||||||
|
<Text type="md" style={pal.textLight}>
|
||||||
|
Allows access to direct messages
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
<FontAwesomeIcon icon={['far', 'trash-can']} style={styles.trashIcon} />
|
<FontAwesomeIcon icon={['far', 'trash-can']} style={styles.trashIcon} />
|
||||||
|
|
||||||
|
|
|
@ -34,10 +34,10 @@
|
||||||
jsonpointer "^5.0.0"
|
jsonpointer "^5.0.0"
|
||||||
leven "^3.1.0"
|
leven "^3.1.0"
|
||||||
|
|
||||||
"@atproto/api@^0.12.11":
|
"@atproto/api@^0.12.13":
|
||||||
version "0.12.11"
|
version "0.12.13"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.11.tgz#d117a0c81395153289e99bafa760a05c2836896f"
|
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.13.tgz#269d6c57ea894e23f20b28bd3cbfed944bd28528"
|
||||||
integrity sha512-NABsZ4ZYznWisr1bGuP6Z4X1GTiu5gNrmAQTxWp45M8RX88BFP1PskoG3J42d2iiyQMVBwTdoENTFYzvsKBuQg==
|
integrity sha512-pRSID6w8AUiZJoCxgctMPRTSGVFHq7wphAnxEbRLBP3OQ1g+BRZUcqFw+e+17Pd3wrc8VImjiD4HCWtCJvCx3w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@atproto/common-web" "^0.3.0"
|
"@atproto/common-web" "^0.3.0"
|
||||||
"@atproto/lexicon" "^0.4.0"
|
"@atproto/lexicon" "^0.4.0"
|
||||||
|
|
Loading…
Reference in New Issue