Refactor sign in policy (#1085)
This commit is contained in:
parent
9ebcbe9728
commit
645eb6c516
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)")))
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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] = ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue