Skip to content

Commit

Permalink
Allow logging in and out
Browse files Browse the repository at this point in the history
  • Loading branch information
LaurentTreguier committed Aug 22, 2024
1 parent b317b99 commit a45d792
Show file tree
Hide file tree
Showing 30 changed files with 744 additions and 308 deletions.
44 changes: 28 additions & 16 deletions Fyreplace.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@
4D13AF812C4E907200845FDB /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D13AF802C4E907200845FDB /* Environment.swift */; };
4D39A4C82BF516B7003FA52E /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 4D39A4C72BF516B7003FA52E /* Localizable.xcstrings */; };
4D4D394A2C086DA2007196D2 /* PublishedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D4D39492C086DA2007196D2 /* PublishedScreen.swift */; };
4D51F2802C621ADB0018E76E /* StatefulProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D51F27F2C621ADB0018E76E /* StatefulProtocol.swift */; };
4D51F2802C621ADB0018E76E /* ViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D51F27F2C621ADB0018E76E /* ViewProtocol.swift */; };
4D51F2842C621B8D0018E76E /* EventBus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D51F2832C621B8D0018E76E /* EventBus.swift */; };
4D51F2862C6232A30018E76E /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D51F2852C6232A30018E76E /* Event.swift */; };
4D5251EC2C1097A600018CD2 /* Destination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D5251EB2C1097A600018CD2 /* Destination.swift */; };
4D5251EF2C1098E900018CD2 /* MultiChoiceScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D5251EE2C1098E900018CD2 /* MultiChoiceScreen.swift */; };
4D5251F12C109D0D00018CD2 /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D5251F02C109D0D00018CD2 /* Screen.swift */; };
4D5251F32C109FAC00018CD2 /* Label+Destination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D5251F22C109FAC00018CD2 /* Label+Destination.swift */; };
4D5251F52C10A9F100018CD2 /* DynamicNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D5251F42C10A9F100018CD2 /* DynamicNavigation.swift */; };
4D53485C2C66423C0001EFDE /* LoadingViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D53485B2C66423C0001EFDE /* LoadingViewState.swift */; };
4D5348612C6646F80001EFDE /* StoringEventBus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D5348602C6646F80001EFDE /* StoringEventBus.swift */; };
4D54C92C2BF2608A001DE071 /* FyreplaceApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D54C92B2BF2608A001DE071 /* FyreplaceApp.swift */; };
4D54C92E2BF2608A001DE071 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D54C92D2BF2608A001DE071 /* MainView.swift */; };
Expand All @@ -50,6 +49,12 @@
4DB10B502C4FEBFC00634BF6 /* HelpCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DB10B4F2C4FEBFC00634BF6 /* HelpCommands.swift */; };
4DB2E36D2C416611007F958D /* SubmitButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DB2E36C2C416611007F958D /* SubmitButton.swift */; };
4DB2E36F2C418F5C007F958D /* DynamicForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DB2E36E2C418F5C007F958D /* DynamicForm.swift */; };
4DC5B1CA2C6FA23000B75A07 /* LoginScreenProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC5B1C92C6FA23000B75A07 /* LoginScreenProtocol.swift */; };
4DC5B1CD2C6FA28E00B75A07 /* RegisterScreenProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC5B1CC2C6FA28E00B75A07 /* RegisterScreenProtocol.swift */; };
4DC5B1CF2C6FA2BE00B75A07 /* MainViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC5B1CE2C6FA2BE00B75A07 /* MainViewProtocol.swift */; };
4DC5B1D62C6FEA2100B75A07 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC5B1D52C6FEA2100B75A07 /* Keychain.swift */; };
4DC5B1D92C720B9800B75A07 /* AuthenticationMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC5B1D82C720B9800B75A07 /* AuthenticationMiddleware.swift */; };
4DC5B1E02C7277B300B75A07 /* AuthenticatingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC5B1DF2C7277B300B75A07 /* AuthenticatingScreen.swift */; };
4DCE062B2C08E5E200F69AF1 /* CompactNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DCE062A2C08E5E200F69AF1 /* CompactNavigation.swift */; };
4DCE062D2C08E65300F69AF1 /* RegularNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DCE062C2C08E65300F69AF1 /* RegularNavigation.swift */; };
4DE140CD2C52688000A699AE /* DestinationCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DE140CC2C52687F00A699AE /* DestinationCommands.swift */; };
Expand Down Expand Up @@ -87,7 +92,7 @@
4D13AF802C4E907200845FDB /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = "<group>"; };
4D39A4C72BF516B7003FA52E /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
4D4D39492C086DA2007196D2 /* PublishedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishedScreen.swift; sourceTree = "<group>"; };
4D51F27F2C621ADB0018E76E /* StatefulProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatefulProtocol.swift; sourceTree = "<group>"; };
4D51F27F2C621ADB0018E76E /* ViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewProtocol.swift; sourceTree = "<group>"; };
4D51F2832C621B8D0018E76E /* EventBus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBus.swift; sourceTree = "<group>"; };
4D51F2852C6232A30018E76E /* Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = "<group>"; };
4D5251A22C0A78F800018CD2 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = "<group>"; };
Expand All @@ -96,7 +101,6 @@
4D5251F02C109D0D00018CD2 /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; };
4D5251F22C109FAC00018CD2 /* Label+Destination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Label+Destination.swift"; sourceTree = "<group>"; };
4D5251F42C10A9F100018CD2 /* DynamicNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicNavigation.swift; sourceTree = "<group>"; };
4D53485B2C66423C0001EFDE /* LoadingViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewState.swift; sourceTree = "<group>"; };
4D5348602C6646F80001EFDE /* StoringEventBus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoringEventBus.swift; sourceTree = "<group>"; };
4D54C9282BF2608A001DE071 /* Fyreplace.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Fyreplace.app; sourceTree = BUILT_PRODUCTS_DIR; };
4D54C92B2BF2608A001DE071 /* FyreplaceApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FyreplaceApp.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -132,6 +136,12 @@
4DB10B4F2C4FEBFC00634BF6 /* HelpCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpCommands.swift; sourceTree = "<group>"; };
4DB2E36C2C416611007F958D /* SubmitButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubmitButton.swift; sourceTree = "<group>"; };
4DB2E36E2C418F5C007F958D /* DynamicForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicForm.swift; sourceTree = "<group>"; };
4DC5B1C92C6FA23000B75A07 /* LoginScreenProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenProtocol.swift; sourceTree = "<group>"; };
4DC5B1CC2C6FA28E00B75A07 /* RegisterScreenProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterScreenProtocol.swift; sourceTree = "<group>"; };
4DC5B1CE2C6FA2BE00B75A07 /* MainViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewProtocol.swift; sourceTree = "<group>"; };
4DC5B1D52C6FEA2100B75A07 /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
4DC5B1D82C720B9800B75A07 /* AuthenticationMiddleware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationMiddleware.swift; sourceTree = "<group>"; };
4DC5B1DF2C7277B300B75A07 /* AuthenticatingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticatingScreen.swift; sourceTree = "<group>"; };
4DCE062A2C08E5E200F69AF1 /* CompactNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactNavigation.swift; sourceTree = "<group>"; };
4DCE062C2C08E65300F69AF1 /* RegularNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegularNavigation.swift; sourceTree = "<group>"; };
4DCE06312C09E19400F69AF1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -175,6 +185,8 @@
isa = PBXGroup;
children = (
4D13AF802C4E907200845FDB /* Environment.swift */,
4DC5B1D52C6FEA2100B75A07 /* Keychain.swift */,
4DC5B1D82C720B9800B75A07 /* AuthenticationMiddleware.swift */,
);
path = Data;
sourceTree = "<group>";
Expand All @@ -195,13 +207,16 @@
children = (
4D5251F02C109D0D00018CD2 /* Screen.swift */,
4D5251EE2C1098E900018CD2 /* MultiChoiceScreen.swift */,
4DC5B1DF2C7277B300B75A07 /* AuthenticatingScreen.swift */,
4D54C9682BF4E8F4001DE071 /* FeedScreen.swift */,
4D54C96A2BF4E97E001DE071 /* NotificationsScreen.swift */,
4D54C96C2BF4E9BE001DE071 /* ArchiveScreen.swift */,
4D54C96E2BF4E9DF001DE071 /* DraftsScreen.swift */,
4D4D39492C086DA2007196D2 /* PublishedScreen.swift */,
4D54C9702BF4EA15001DE071 /* SettingsScreen.swift */,
4DC5B1C92C6FA23000B75A07 /* LoginScreenProtocol.swift */,
4D9B3B372C334B3A00A8F7AD /* LoginScreen.swift */,
4DC5B1CC2C6FA28E00B75A07 /* RegisterScreenProtocol.swift */,
4D9B3B392C334B6300A8F7AD /* RegisterScreen.swift */,
);
path = Screens;
Expand Down Expand Up @@ -268,7 +283,6 @@
4D13AF7C2C4E904E00845FDB /* Data */,
4D51F2822C621B7D0018E76E /* Events */,
4D54C95E2BF27C78001DE071 /* Views */,
4DFB906A2C58FC4400D4DABF /* States */,
4DB10B4E2C4FEBCE00634BF6 /* Commands */,
4DA7BFB92C5FDEA1005CC4FF /* Fakes */,
4D54C92B2BF2608A001DE071 /* FyreplaceApp.swift */,
Expand Down Expand Up @@ -325,6 +339,8 @@
4D9B3B3B2C34B11D00A8F7AD /* Forms */,
4D5251E82C10532100018CD2 /* Navigation */,
4D4D394B2C086DD3007196D2 /* Screens */,
4D51F27F2C621ADB0018E76E /* ViewProtocol.swift */,
4DC5B1CE2C6FA2BE00B75A07 /* MainViewProtocol.swift */,
4D54C92D2BF2608A001DE071 /* MainView.swift */,
);
path = Views;
Expand Down Expand Up @@ -393,15 +409,6 @@
path = Config;
sourceTree = "<group>";
};
4DFB906A2C58FC4400D4DABF /* States */ = {
isa = PBXGroup;
children = (
4D51F27F2C621ADB0018E76E /* StatefulProtocol.swift */,
4D53485B2C66423C0001EFDE /* LoadingViewState.swift */,
);
path = States;
sourceTree = "<group>";
};
4DFB906E2C5908BF00D4DABF /* Screens */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -587,16 +594,19 @@
4DA7BFBB2C5FDEC1005CC4FF /* FakeClient.swift in Sources */,
4D54C9712BF4EA15001DE071 /* SettingsScreen.swift in Sources */,
4D13AF7B2C4E8F4200845FDB /* EnvironmentPicker.swift in Sources */,
4DC5B1CF2C6FA2BE00B75A07 /* MainViewProtocol.swift in Sources */,
4D9B3B422C36E23A00A8F7AD /* Array+RawRepresentable.swift in Sources */,
4D4D394A2C086DA2007196D2 /* PublishedScreen.swift in Sources */,
4D5251EC2C1097A600018CD2 /* Destination.swift in Sources */,
4D54C92E2BF2608A001DE071 /* MainView.swift in Sources */,
4D5251F12C109D0D00018CD2 /* Screen.swift in Sources */,
4DE140CD2C52688000A699AE /* DestinationCommands.swift in Sources */,
4D54C96B2BF4E97E001DE071 /* NotificationsScreen.swift in Sources */,
4D51F2802C621ADB0018E76E /* StatefulProtocol.swift in Sources */,
4D51F2802C621ADB0018E76E /* ViewProtocol.swift in Sources */,
4DC5B1D92C720B9800B75A07 /* AuthenticationMiddleware.swift in Sources */,
4D9B3B472C36F50300A8F7AD /* UITextContentType.swift in Sources */,
4D9B3B382C334B3A00A8F7AD /* LoginScreen.swift in Sources */,
4DC5B1E02C7277B300B75A07 /* AuthenticatingScreen.swift in Sources */,
4D5251EF2C1098E900018CD2 /* MultiChoiceScreen.swift in Sources */,
4DB10B502C4FEBFC00634BF6 /* HelpCommands.swift in Sources */,
4D9B3B452C36F46F00A8F7AD /* NSTextContentType.swift in Sources */,
Expand All @@ -606,11 +616,13 @@
4DCE062B2C08E5E200F69AF1 /* CompactNavigation.swift in Sources */,
4DCE062D2C08E65300F69AF1 /* RegularNavigation.swift in Sources */,
4D54C9692BF4E8F4001DE071 /* FeedScreen.swift in Sources */,
4D53485C2C66423C0001EFDE /* LoadingViewState.swift in Sources */,
4DC5B1D62C6FEA2100B75A07 /* Keychain.swift in Sources */,
4D9B3B3A2C334B6300A8F7AD /* RegisterScreen.swift in Sources */,
4DB2E36D2C416611007F958D /* SubmitButton.swift in Sources */,
4D54C96D2BF4E9BE001DE071 /* ArchiveScreen.swift in Sources */,
4DC5B1CD2C6FA28E00B75A07 /* RegisterScreenProtocol.swift in Sources */,
4D13AF812C4E907200845FDB /* Environment.swift in Sources */,
4DC5B1CA2C6FA23000B75A07 /* LoginScreenProtocol.swift in Sources */,
4D51F2842C621B8D0018E76E /* EventBus.swift in Sources */,
4D5251F52C10A9F100018CD2 /* DynamicNavigation.swift in Sources */,
4D9DC5032C11BF2500BA0507 /* Config.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions Fyreplace/Config/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ struct Config {
}

struct App {
let name: String
let info: Info
let api: Api

init(_ data: [String: Any]) {
name = data.string("Name")!
info = .init(data.dictionary("Info")!)
api = .init(data.dictionary("Api")!)
}
Expand Down
2 changes: 2 additions & 0 deletions Fyreplace/Config/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<dict>
<key>App</key>
<dict>
<key>Name</key>
<string>$(PRODUCT_NAME)</string>
<key>Api</key>
<dict>
<key>Dev</key>
Expand Down
103 changes: 103 additions & 0 deletions Fyreplace/Data/Keychain.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import Foundation
import Security
import SwiftUI

struct Keychain {
let service: String

private var query: [CFString: Any] {
[
kSecClass: kSecClassGenericPassword,
kSecMatchLimit: kSecMatchLimitOne,
kSecAttrLabel: Config.default.app.name,
kSecAttrService: service,
]
}

func get() -> String {
var info = query
info[kSecReturnData] = true
var itemReference: CFTypeRef?
guard SecItemCopyMatching(info as CFDictionary, &itemReference) == errSecSuccess,
let data = itemReference as? Data,
let value = String(data: data, encoding: .utf8)
else { return "" }
return value
}

@KeychainActor
func set(_ value: String) -> Bool {
guard !value.isEmpty else { return delete() }
let data = value.data(using: .utf8)

if SecItemUpdate(query as CFDictionary, [kSecValueData: data] as CFDictionary) == errSecSuccess {
return true
} else {
var info = query
info[kSecValueData] = data
return SecItemAdd(info as CFDictionary, nil) == errSecSuccess
}
}

@KeychainActor
func delete() -> Bool {
return SecItemDelete(query as CFDictionary) == errSecSuccess
}
}

@globalActor
actor KeychainActor: GlobalActor {
static let shared = KeychainActor()
}

@propertyWrapper
struct KeychainStorage: DynamicProperty {
@ObservedObject
private var cache: KeychainCache

private let keychain: Keychain

var wrappedValue: String {
get {
cache.value
}

nonmutating set(value) {
cache.value = value

Task {
await keychain.set(value)
}
}
}

init(_ key: String) {
let cleanKey = key.replacing(".", with: ":")
keychain = .init(service: cleanKey)
cache = .shared(for: cleanKey, defaultValue: keychain.get())
}
}

class KeychainCache: ObservableObject {
private static var instances: [String: KeychainCache] = [:]

private let key: String

@Published
var value: String

init(key: String, defaultValue: String) {
self.key = key
value = defaultValue
}

static func shared(for key: String, defaultValue: String) -> KeychainCache {
if let instance = instances[key] {
return instance
}

let instance = KeychainCache(key: key, defaultValue: defaultValue)
instances[key] = instance
return instance
}
}
4 changes: 2 additions & 2 deletions Fyreplace/Fakes/FakeClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ extension FakeClient {
extension FakeClient {
static let badIdentifer = "bad-identifier"
static let goodIdentifer = "good-identifier"
static let badSecret = "bad-secret"
static let goodSecret = "good-secret"
static let badSecret = "000000"
static let goodSecret = "123456"
static let badToken = "bad-token"
static let goodToken = "good-token"

Expand Down
70 changes: 70 additions & 0 deletions Fyreplace/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@
}
}
},
"Cancel" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cancel"
}
}
}
},
"Environment.Default" : {
"localizations" : {
"en" : {
Expand Down Expand Up @@ -134,6 +144,26 @@
}
}
},
"Login.Error.BadRequest.Message" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Check if the code you provided is correct."
}
}
}
},
"Login.Error.BadRequest.Title" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Invalid code"
}
}
}
},
"Login.Error.NotFound.Message" : {
"localizations" : {
"en" : {
Expand Down Expand Up @@ -164,6 +194,16 @@
}
}
},
"Login.Help.RandomCode" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "A one-time use code has been sent to your email address."
}
}
}
},
"Login.Identifier" : {
"localizations" : {
"en" : {
Expand All @@ -184,6 +224,26 @@
}
}
},
"Login.RandomCode" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "One-time code"
}
}
}
},
"Login.RandomCode.Prompt" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "123456"
}
}
}
},
"Login.Submit" : {
"localizations" : {
"en" : {
Expand Down Expand Up @@ -373,6 +433,16 @@
}
}
}
},
"Settings.Logout" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Logout"
}
}
}
}
},
"version" : "1.0"
Expand Down
Loading

0 comments on commit a45d792

Please sign in to comment.