Refactor sign in policy (#1085)

This commit is contained in:
Daniel Chick 2024-06-08 22:22:18 -05:00 committed by GitHub
parent 9ebcbe9728
commit 645eb6c516
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 54 additions and 51 deletions

View File

@ -15,7 +15,7 @@ final class UserSignInCoordinator: NavigationCoordinatable {
struct SecurityParameters { struct SecurityParameters {
let pinHint: Binding<String> let pinHint: Binding<String>
let signInPolicy: Binding<UserAccessPolicy> let accessPolicy: Binding<UserAccessPolicy>
} }
let stack = NavigationStack(initial: \UserSignInCoordinator.start) let stack = NavigationStack(initial: \UserSignInCoordinator.start)
@ -48,7 +48,7 @@ final class UserSignInCoordinator: NavigationCoordinatable {
NavigationViewCoordinator { NavigationViewCoordinator {
UserSignInView.SecurityView( UserSignInView.SecurityView(
pinHint: parameters.pinHint, pinHint: parameters.pinHint,
signInPolicy: parameters.signInPolicy accessPolicy: parameters.accessPolicy
) )
} }
} }

View File

@ -42,7 +42,7 @@ extension StoredValues.Keys {
enum Temp { enum Temp {
static let userSignInPolicy: Key<UserAccessPolicy> = TempKey( static let userAccessPolicy: Key<UserAccessPolicy> = TempKey(
"userSignInPolicy", "userSignInPolicy",
ownerID: "temporary", ownerID: "temporary",
domain: "userSignInPolicy", domain: "userSignInPolicy",

View File

@ -72,8 +72,7 @@ extension UserState {
} }
} }
// TODO: rename to accessPolicy and fix all uses var accessPolicy: UserAccessPolicy {
var signInPolicy: UserAccessPolicy {
get { get {
StoredValues[.User.accessPolicy(id: id)] StoredValues[.User.accessPolicy(id: id)]
} }

View File

@ -77,7 +77,7 @@ class SelectUserViewModel: ViewModel, Eventful, Stateful {
} }
case let .signIn(user, pin): case let .signIn(user, pin):
if user.signInPolicy == .requirePin, let storedPin = keychain.get("\(user.id)-pin") { if user.accessPolicy == .requirePin, let storedPin = keychain.get("\(user.id)-pin") {
if pin != storedPin { if pin != storedPin {
eventSubject.send(.error(.init("Incorrect pin for \(user.username)"))) eventSubject.send(.error(.init("Incorrect pin for \(user.username)")))

View File

@ -31,7 +31,7 @@ class UserLocalSecurityViewModel: ViewModel, Eventful {
// Will throw and send event if needing to prompt for old auth. // Will throw and send event if needing to prompt for old auth.
func checkForOldPolicy() throws { func checkForOldPolicy() throws {
let oldPolicy = userSession.user.signInPolicy let oldPolicy = userSession.user.accessPolicy
switch oldPolicy { switch oldPolicy {
case .requireDeviceAuthentication: case .requireDeviceAuthentication:
@ -75,7 +75,7 @@ class UserLocalSecurityViewModel: ViewModel, Eventful {
keychain.delete(StoredValues[.Temp.userLocalPin]) keychain.delete(StoredValues[.Temp.userLocalPin])
} }
userSession.user.signInPolicy = newPolicy userSession.user.accessPolicy = newPolicy
userSession.user.pinHint = newPinHint userSession.user.pinHint = newPinHint
} }
} }

View File

@ -92,7 +92,7 @@ final class UserSignInViewModel: ViewModel, Eventful, Stateful {
guard let self else { return } guard let self else { return }
Task { Task {
await self.send(.signInQuickConnect(secret: secret, policy: StoredValues[.Temp.userSignInPolicy])) await self.send(.signInQuickConnect(secret: secret, policy: StoredValues[.Temp.userAccessPolicy]))
} }
} }
} }
@ -236,7 +236,7 @@ final class UserSignInViewModel: ViewModel, Eventful, Stateful {
} }
StoredValues[.Temp.userData] = userData StoredValues[.Temp.userData] = userData
StoredValues[.Temp.userSignInPolicy] = policy StoredValues[.Temp.userAccessPolicy] = policy
let newState = UserState( let newState = UserState(
id: id, id: id,
@ -263,7 +263,7 @@ final class UserSignInViewModel: ViewModel, Eventful, Stateful {
} }
StoredValues[.Temp.userData] = userData StoredValues[.Temp.userData] = userData
StoredValues[.Temp.userSignInPolicy] = policy StoredValues[.Temp.userAccessPolicy] = policy
let newState = UserState( let newState = UserState(
id: id, id: id,
@ -304,13 +304,13 @@ final class UserSignInViewModel: ViewModel, Eventful, Stateful {
} }
user.data = StoredValues[.Temp.userData] user.data = StoredValues[.Temp.userData]
user.signInPolicy = StoredValues[.Temp.userSignInPolicy] user.accessPolicy = StoredValues[.Temp.userAccessPolicy]
keychain.set(StoredValues[.Temp.userLocalPin], forKey: "\(user.id)-pin") keychain.set(StoredValues[.Temp.userLocalPin], forKey: "\(user.id)-pin")
user.pinHint = StoredValues[.Temp.userLocalPinHint] user.pinHint = StoredValues[.Temp.userLocalPinHint]
// TODO: remove when implemented periodic cleanup elsewhere // TODO: remove when implemented periodic cleanup elsewhere
StoredValues[.Temp.userSignInPolicy] = .none StoredValues[.Temp.userAccessPolicy] = .none
StoredValues[.Temp.userLocalPin] = "" StoredValues[.Temp.userLocalPin] = ""
StoredValues[.Temp.userLocalPinHint] = "" StoredValues[.Temp.userLocalPinHint] = ""
} }

View File

@ -139,7 +139,7 @@ struct SelectUserView: View {
Task { @MainActor in Task { @MainActor in
selectedUsers.insert(user) selectedUsers.insert(user)
switch user.signInPolicy { switch user.accessPolicy {
case .requireDeviceAuthentication: case .requireDeviceAuthentication:
try await performDeviceAuthentication(reason: "User \(user.username) requires device authentication") try await performDeviceAuthentication(reason: "User \(user.username) requires device authentication")
case .requirePin: case .requirePin:

View File

@ -158,7 +158,7 @@ struct UserLocalSecurityView: View {
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.onFirstAppear { .onFirstAppear {
pinHint = viewModel.userSession.user.pinHint pinHint = viewModel.userSession.user.pinHint
signInPolicy = viewModel.userSession.user.signInPolicy signInPolicy = viewModel.userSession.user.accessPolicy
} }
.onReceive(viewModel.events) { event in .onReceive(viewModel.events) { event in
switch event { switch event {
@ -210,7 +210,7 @@ struct UserLocalSecurityView: View {
checkOldPolicy() checkOldPolicy()
} label: { } label: {
Group { Group {
if signInPolicy == .requirePin, signInPolicy == viewModel.userSession.user.signInPolicy { if signInPolicy == .requirePin, signInPolicy == viewModel.userSession.user.accessPolicy {
Text("Change Pin") Text("Change Pin")
} else { } else {
Text("Save") Text("Save")

View File

@ -20,7 +20,7 @@ extension UserSignInView {
@Binding @Binding
private var pinHint: String private var pinHint: String
@Binding @Binding
private var signInPolicy: UserAccessPolicy private var accessPolicy: UserAccessPolicy
@State @State
private var listSize: CGSize = .zero private var listSize: CGSize = .zero
@ -31,12 +31,12 @@ extension UserSignInView {
init( init(
pinHint: Binding<String>, pinHint: Binding<String>,
signInPolicy: Binding<UserAccessPolicy> accessPolicy: Binding<UserAccessPolicy>
) { ) {
self._pinHint = pinHint self._pinHint = pinHint
self._signInPolicy = signInPolicy self._accessPolicy = accessPolicy
self._updatePinHint = State(initialValue: pinHint.wrappedValue) self._updatePinHint = State(initialValue: pinHint.wrappedValue)
self._updateSignInPolicy = State(initialValue: signInPolicy.wrappedValue) self._updateSignInPolicy = State(initialValue: accessPolicy.wrappedValue)
} }
var body: some View { var body: some View {
@ -82,7 +82,7 @@ extension UserSignInView {
} }
} }
if signInPolicy == .requirePin { if accessPolicy == .requirePin {
Section { Section {
TextField("Hint", text: $updatePinHint) TextField("Hint", text: $updatePinHint)
} header: { } header: {
@ -92,7 +92,7 @@ extension UserSignInView {
} }
} }
} }
.animation(.linear, value: signInPolicy) .animation(.linear, value: accessPolicy)
.navigationTitle("Security") .navigationTitle("Security")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.navigationBarCloseButton { .navigationBarCloseButton {
@ -107,7 +107,7 @@ extension UserSignInView {
pinHint = newValue pinHint = newValue
} }
.onChange(of: updateSignInPolicy) { newValue in .onChange(of: updateSignInPolicy) { newValue in
signInPolicy = newValue accessPolicy = newValue
} }
.trackingSize($listSize) .trackingSize($listSize)
} }

View File

@ -47,7 +47,7 @@ struct UserSignInView: View {
@State @State
private var pinHint: String = "" private var pinHint: String = ""
@State @State
private var signInPolicy: UserAccessPolicy = .none private var accessPolicy: UserAccessPolicy = .none
@State @State
private var username: String = "" private var username: String = ""
@ -58,11 +58,32 @@ struct UserSignInView: View {
self._viewModel = StateObject(wrappedValue: UserSignInViewModel(server: server)) self._viewModel = StateObject(wrappedValue: UserSignInViewModel(server: server))
} }
private func handleSignIn(_ event: UserSignInViewModel.Event) {
switch event {
case let .duplicateUser(duplicateUser):
UIDevice.impact(.medium)
self.duplicateUser = duplicateUser
isPresentingDuplicateUser = true
case let .error(eventError):
UIDevice.feedback(.error)
error = eventError
isPresentingError = true
case let .signedIn(user):
UIDevice.feedback(.success)
Defaults[.lastSignedInUserID] = user.id
UserSession.current.reset()
Notifications[.didSignIn].post()
}
}
// TODO: don't have multiple ways to handle device authentication vs required pin // TODO: don't have multiple ways to handle device authentication vs required pin
private func openQuickConnect(needsPin: Bool = true) { private func openQuickConnect(needsPin: Bool = true) {
Task { Task {
switch signInPolicy { switch accessPolicy {
case .none: () case .none: ()
case .requireDeviceAuthentication: case .requireDeviceAuthentication:
try await performDeviceAuthentication( try await performDeviceAuthentication(
@ -84,27 +105,27 @@ struct UserSignInView: View {
private func signInUserPassword(needsPin: Bool = true) { private func signInUserPassword(needsPin: Bool = true) {
Task { Task {
switch signInPolicy { switch accessPolicy {
case .none: () case .none: ()
case .requireDeviceAuthentication: case .requireDeviceAuthentication:
try await performDeviceAuthentication(reason: "Require device authentication to sign in to \(username) on this device") try await performDeviceAuthentication(reason: "Require device authentication to sign in to \(username) on this device")
case .requirePin: case .requirePin:
if needsPin { if needsPin {
onPinCompletion = { onPinCompletion = {
viewModel.send(.signIn(username: username, password: password, policy: signInPolicy)) viewModel.send(.signIn(username: username, password: password, policy: accessPolicy))
} }
isPresentingLocalPin = true isPresentingLocalPin = true
return return
} }
} }
viewModel.send(.signIn(username: username, password: password, policy: signInPolicy)) viewModel.send(.signIn(username: username, password: password, policy: accessPolicy))
} }
} }
private func signInUplicate(user: UserState, needsPin: Bool = true, replace: Bool) { private func signInUplicate(user: UserState, needsPin: Bool = true, replace: Bool) {
Task { Task {
switch user.signInPolicy { switch user.accessPolicy {
case .none: () case .none: ()
case .requireDeviceAuthentication: case .requireDeviceAuthentication:
try await performDeviceAuthentication(reason: "User \(user.username) requires device authentication") try await performDeviceAuthentication(reason: "User \(user.username) requires device authentication")
@ -185,7 +206,7 @@ struct UserSignInView: View {
} header: { } header: {
Text(L10n.signInToServer(viewModel.server.name)) Text(L10n.signInToServer(viewModel.server.name))
} footer: { } footer: {
switch signInPolicy { switch accessPolicy {
case .requireDeviceAuthentication: case .requireDeviceAuthentication:
HStack { HStack {
Image(systemName: "exclamationmark.circle.fill") Image(systemName: "exclamationmark.circle.fill")
@ -298,30 +319,13 @@ struct UserSignInView: View {
.onChange(of: pinHint) { newValue in .onChange(of: pinHint) { newValue in
StoredValues[.Temp.userLocalPinHint] = newValue StoredValues[.Temp.userLocalPinHint] = newValue
} }
.onChange(of: signInPolicy) { newValue in .onChange(of: accessPolicy) { newValue in
// necessary for Quick Connect sign in, but could // necessary for Quick Connect sign in, but could
// just use for general sign in // just use for general sign in
StoredValues[.Temp.userSignInPolicy] = newValue StoredValues[.Temp.userAccessPolicy] = newValue
} }
.onReceive(viewModel.events) { event in .onReceive(viewModel.events) { event in
switch event { handleSignIn(event)
case let .duplicateUser(duplicateUser):
UIDevice.impact(.medium)
self.duplicateUser = duplicateUser
isPresentingDuplicateUser = true
case let .error(eventError):
UIDevice.feedback(.error)
error = eventError
isPresentingError = true
case let .signedIn(user):
UIDevice.feedback(.success)
Defaults[.lastSignedInUserID] = user.id
UserSession.current.reset()
Notifications[.didSignIn].post()
}
} }
.onFirstAppear { .onFirstAppear {
focusedTextField = 0 focusedTextField = 0
@ -335,7 +339,7 @@ struct UserSignInView: View {
Button("Security", systemImage: "gearshape.fill") { Button("Security", systemImage: "gearshape.fill") {
let parameters = UserSignInCoordinator.SecurityParameters( let parameters = UserSignInCoordinator.SecurityParameters(
pinHint: $pinHint, pinHint: $pinHint,
signInPolicy: $signInPolicy accessPolicy: $accessPolicy
) )
router.route(to: \.security, parameters) router.route(to: \.security, parameters)
} }