mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-29 17:39:27 +01:00
FEATURE: add plain text password toggle to settings (#22)
* FEATURE: add plain text password toggle to settings * clean up state types, lint, and add migrate Co-authored-by: austinried <4966622+austinried@users.noreply.github.com>
This commit is contained in:
parent
37214fcbdc
commit
9a6f8b86fc
@ -1,9 +1,19 @@
|
|||||||
import { GetAlbumList2Type } from '@app/subsonic/params'
|
import { GetAlbumList2Type } from '@app/subsonic/params'
|
||||||
|
|
||||||
export interface Server {
|
export type Server = (TokenPassword | PlainPassword) & {
|
||||||
id: string
|
id: string
|
||||||
address: string
|
address: string
|
||||||
username: string
|
username: string
|
||||||
|
usePlainPassword: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PlainPassword {
|
||||||
|
usePlainPassword: true
|
||||||
|
plainPassword: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TokenPassword {
|
||||||
|
usePlainPassword: false
|
||||||
token: string
|
token: string
|
||||||
salt: string
|
salt: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,9 @@ import md5 from 'md5'
|
|||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { StyleSheet, Text, TextInput, View, ViewStyle } from 'react-native'
|
import { StyleSheet, Text, TextInput, View, ViewStyle } from 'react-native'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import SettingsSwitch from '@app/components/SettingsSwitch'
|
||||||
|
|
||||||
|
const PASSWORD_PLACEHOLDER = 'PASSWORD_PLACEHOLDER'
|
||||||
|
|
||||||
const ServerView: React.FC<{
|
const ServerView: React.FC<{
|
||||||
id?: string
|
id?: string
|
||||||
@ -26,7 +29,12 @@ const ServerView: React.FC<{
|
|||||||
|
|
||||||
const [address, setAddress] = useState(server?.address || '')
|
const [address, setAddress] = useState(server?.address || '')
|
||||||
const [username, setUsername] = useState(server?.username || '')
|
const [username, setUsername] = useState(server?.username || '')
|
||||||
const [password, setPassword] = useState(server?.token ? 'password' : '')
|
|
||||||
|
const [usePlainPassword, setUsePlainPassword] = useState(server?.usePlainPassword ?? false)
|
||||||
|
const [password, setPassword] = useState(
|
||||||
|
server?.usePlainPassword ? server.plainPassword || '' : server?.token ? PASSWORD_PLACEHOLDER : '',
|
||||||
|
)
|
||||||
|
|
||||||
const [testing, setTesting] = useState(false)
|
const [testing, setTesting] = useState(false)
|
||||||
const [removing, setRemoving] = useState(false)
|
const [removing, setRemoving] = useState(false)
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
@ -48,11 +56,24 @@ const ServerView: React.FC<{
|
|||||||
}, [navigation])
|
}, [navigation])
|
||||||
|
|
||||||
const createServer = useCallback<() => Server>(() => {
|
const createServer = useCallback<() => Server>(() => {
|
||||||
const salt = server?.salt || uuidv4()
|
if (usePlainPassword) {
|
||||||
|
return {
|
||||||
|
id: server?.id || uuidv4(),
|
||||||
|
usePlainPassword,
|
||||||
|
plainPassword: password,
|
||||||
|
address,
|
||||||
|
username,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let token: string
|
let token: string
|
||||||
if (password === 'password' && server?.token) {
|
let salt: string
|
||||||
|
|
||||||
|
if (server && !server.usePlainPassword && password === PASSWORD_PLACEHOLDER) {
|
||||||
|
salt = server.salt
|
||||||
token = server.token
|
token = server.token
|
||||||
} else {
|
} else {
|
||||||
|
salt = uuidv4()
|
||||||
token = md5(password + salt)
|
token = md5(password + salt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,10 +81,11 @@ const ServerView: React.FC<{
|
|||||||
id: server?.id || uuidv4(),
|
id: server?.id || uuidv4(),
|
||||||
address,
|
address,
|
||||||
username,
|
username,
|
||||||
|
usePlainPassword,
|
||||||
salt,
|
salt,
|
||||||
token,
|
token,
|
||||||
}
|
}
|
||||||
}, [address, password, server?.id, server?.salt, server?.token, username])
|
}, [usePlainPassword, server, address, username, password])
|
||||||
|
|
||||||
const save = useCallback(() => {
|
const save = useCallback(() => {
|
||||||
if (!validate()) {
|
if (!validate()) {
|
||||||
@ -105,6 +127,25 @@ const ServerView: React.FC<{
|
|||||||
waitForRemove()
|
waitForRemove()
|
||||||
}, [canRemove, exit, id, removeServer])
|
}, [canRemove, exit, id, removeServer])
|
||||||
|
|
||||||
|
const togglePlainPassword = useCallback(
|
||||||
|
(value: boolean) => {
|
||||||
|
setUsePlainPassword(value)
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
if (server && server.usePlainPassword) {
|
||||||
|
setPassword(server.plainPassword)
|
||||||
|
} else if (server) {
|
||||||
|
setPassword('')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (server && !server.usePlainPassword) {
|
||||||
|
setPassword(PASSWORD_PLACEHOLDER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[server],
|
||||||
|
)
|
||||||
|
|
||||||
const test = useCallback(() => {
|
const test = useCallback(() => {
|
||||||
setTesting(true)
|
setTesting(true)
|
||||||
const potential = createServer()
|
const potential = createServer()
|
||||||
@ -180,6 +221,16 @@ const ServerView: React.FC<{
|
|||||||
value={password}
|
value={password}
|
||||||
onChangeText={setPassword}
|
onChangeText={setPassword}
|
||||||
/>
|
/>
|
||||||
|
<SettingsSwitch
|
||||||
|
title="Force plain text password"
|
||||||
|
subtitle={
|
||||||
|
usePlainPassword
|
||||||
|
? 'Send password in plain text (legacy, make sure your connection is secure!)'
|
||||||
|
: 'Send password as token + salt'
|
||||||
|
}
|
||||||
|
value={usePlainPassword}
|
||||||
|
setValue={togglePlainPassword}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
disabled={disableControls()}
|
disabled={disableControls()}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
|
|||||||
11
app/state/migrations.ts
Normal file
11
app/state/migrations.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
const migrations: Array<(state: any) => any> = [
|
||||||
|
state => {
|
||||||
|
for (let server of state.settings.servers) {
|
||||||
|
server.usePlainPassword = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default migrations
|
||||||
@ -4,10 +4,13 @@ import AsyncStorage from '@react-native-async-storage/async-storage'
|
|||||||
import create from 'zustand'
|
import create from 'zustand'
|
||||||
import { persist, StateStorage } from 'zustand/middleware'
|
import { persist, StateStorage } from 'zustand/middleware'
|
||||||
import { CacheSlice, createCacheSlice } from './cache'
|
import { CacheSlice, createCacheSlice } from './cache'
|
||||||
|
import migrations from './migrations'
|
||||||
import { createMusicMapSlice, MusicMapSlice } from './musicmap'
|
import { createMusicMapSlice, MusicMapSlice } from './musicmap'
|
||||||
import { createTrackPlayerSlice, TrackPlayerSlice } from './trackplayer'
|
import { createTrackPlayerSlice, TrackPlayerSlice } from './trackplayer'
|
||||||
import { createTrackPlayerMapSlice, TrackPlayerMapSlice } from './trackplayermap'
|
import { createTrackPlayerMapSlice, TrackPlayerMapSlice } from './trackplayermap'
|
||||||
|
|
||||||
|
const DB_VERSION = migrations.length
|
||||||
|
|
||||||
export type Store = SettingsSlice &
|
export type Store = SettingsSlice &
|
||||||
MusicSlice &
|
MusicSlice &
|
||||||
MusicMapSlice &
|
MusicMapSlice &
|
||||||
@ -51,6 +54,7 @@ export const useStore = create<Store>(
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: '@appStore',
|
name: '@appStore',
|
||||||
|
version: DB_VERSION,
|
||||||
getStorage: () => storage,
|
getStorage: () => storage,
|
||||||
whitelist: ['settings', 'cacheFiles'],
|
whitelist: ['settings', 'cacheFiles'],
|
||||||
onRehydrateStorage: _preState => {
|
onRehydrateStorage: _preState => {
|
||||||
@ -59,6 +63,17 @@ export const useStore = create<Store>(
|
|||||||
postState?.setHydrated(true)
|
postState?.setHydrated(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
migrate: (persistedState, version) => {
|
||||||
|
if (version > DB_VERSION) {
|
||||||
|
throw new Error('cannot migrate db on a downgrade, delete all data first')
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = version; i < DB_VERSION; i++) {
|
||||||
|
persistedState = migrations[i](persistedState)
|
||||||
|
}
|
||||||
|
|
||||||
|
return persistedState
|
||||||
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -64,8 +64,14 @@ export class SubsonicApiClient {
|
|||||||
|
|
||||||
this.params = new URLSearchParams()
|
this.params = new URLSearchParams()
|
||||||
this.params.append('u', server.username)
|
this.params.append('u', server.username)
|
||||||
|
|
||||||
|
if (server.usePlainPassword) {
|
||||||
|
this.params.append('p', server.plainPassword)
|
||||||
|
} else {
|
||||||
this.params.append('t', server.token)
|
this.params.append('t', server.token)
|
||||||
this.params.append('s', server.salt)
|
this.params.append('s', server.salt)
|
||||||
|
}
|
||||||
|
|
||||||
this.params.append('v', '1.15.0')
|
this.params.append('v', '1.15.0')
|
||||||
this.params.append('c', 'subtracks')
|
this.params.append('c', 'subtracks')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,13 +74,6 @@
|
|||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"preset": "react-native",
|
"preset": "react-native",
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
|
||||||
"ts",
|
|
||||||
"tsx",
|
|
||||||
"js",
|
|
||||||
"jsx",
|
|
||||||
"json",
|
|
||||||
"node"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user