Skip to content
This repository was archived by the owner on Jan 2, 2025. It is now read-only.

Commit a5b6b78

Browse files
authored
Merge pull request #3 from hyperlinkgroup/release-1.2
Release 1.2
2 parents 7800c70 + 59a6b55 commit a5b6b78

18 files changed

+388
-70
lines changed

.github/README.md

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ Target for all authentication related operations.
186186
- [x] Authentication by Apple Id
187187
- [x] Error Handling
188188
- [x] Link Anonymous and authenticated Accounts
189-
- [ ] Authentication by Email and Password
189+
- [x] Authentication by Email and Password
190190
- [x] Sign out and Deleting an Account
191191
- [x] UIKit-View for handling SignInWithApple-Requests
192192

@@ -208,7 +208,7 @@ AuthenticationManager.email = "[email protected]" // email is overwritten and c
208208

209209

210210
##### Configuration
211-
By default the `AuthenticationManager` is using Sign In With Apple and allows also anonymous authentication. If you want to disable it, you can use a custom configuration object. With that the AuthenticationManager needs to be initialized on App Start.
211+
By default, the `AuthenticationManager` allows three authentication methods: Sign in with Email and Password, by using the Apple ID and anonymous login. If you want to restrict the providers, you can use a custom configuration object. With that, the AuthenticationManager needs to be initialized on App Start.
212212

213213
You can also link a repository where you manage your users details. If you subclass the `UserRepositoryProtocol`your user's details with the user details you get during the authentication process.
214214

@@ -227,7 +227,7 @@ struct MyApp: App {
227227
FirebaseApp.configure()
228228

229229
var authenticationConfiguration = Configuration()
230-
authenticationConfiguration.allowAnonymousUsers = false
230+
authenticationConfiguration.authProvider = [.signInWithApple, .anonymous, .emailPassword]
231231
authenticationConfiguration.userRepository = MyRepository.shared
232232
AuthenticationManager.setup(authenticationConfiguration)
233233
}
@@ -245,7 +245,7 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau
245245
FirebaseApp.configure()
246246

247247
var authenticationConfiguration = Configuration()
248-
authenticationConfiguration.allowAnonymousUsers = false
248+
authenticationConfiguration.authProvider = [.signInWithApple, .anonymous, .emailPassword]
249249
authenticationConfiguration.userRepository = MyRepository.shared
250250
AuthenticationManager.setup(authenticationConfiguration)
251251

@@ -254,11 +254,40 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau
254254
```
255255

256256

257+
##### Authenticate with Email and Password
258+
```Swift
259+
// Create Account
260+
AuthenticationManager.signUpWithEmail(email: "[email protected]", password: "123") { error in
261+
if let error {
262+
// Check detailed Error Response
263+
} else {
264+
// Account was created and User logged in successfully
265+
}
266+
}
267+
268+
// Login
269+
AuthenticationManager.loginWithEmail(email: "[email protected]", password: "123") { error in
270+
if let error {
271+
// Check detailed Error Response
272+
} else {
273+
// Authentication was successful
274+
}
275+
}
276+
277+
278+
// Update User Information
279+
AuthenticationManager.resetPassword(for: "[email protected]")
280+
AuthenticationManager.updateMail(currentPassword: "123", newMail: "[email protected]") { error in }
281+
AuthenticationManager.updatePassword(currentPassword: "123", newPassword: "456") { error in }
282+
```
283+
284+
257285
##### Authenticate by using Apple Id
258286

259-
The Authentication Manager controls the whole authentication flow and returns you the handled error without any further work.
287+
The Authentication Manager controls the whole authentication flow and returns the handled error without any further work.
260288

261289
###### SwiftUI:
290+
262291
```Swift
263292
import FirebaseAuthenticationManager
264293
import AuthenticationServices
@@ -391,11 +420,22 @@ AuthenticationManager.signOut { error in
391420
##### Delete Account
392421

393422
You can delete a user's account in Firebase.
394-
This can require a reauthentication before executing this method.
423+
This can require a reauthentication before executing this method which is done automatically for users who signed in with email and password.
395424

396425
If your user signed in with its Apple-Account, this requires involving the UI to receive the status of authentication. We don't handle this error (yet), so we advise you to execute an additional login manually before accessing this function.
397426

398427
```Swift
428+
// Account is related to Email and Password
429+
AuthenticationManager.deleteAccount(currentPassword: "123") { error in
430+
if let error {
431+
// Check detailed Error Response
432+
} else {
433+
// Deletion of Account was successful
434+
// If you stored your user's data in a database, don't forget to implement deleting it separately.
435+
}
436+
}
437+
438+
// Account is related to Apple ID
399439
AuthenticationManager.deleteAccount { error in
400440
if let error {
401441
// Check detailed Error Response
@@ -443,7 +483,7 @@ class UserRepository: UserRepositoryProtocol {
443483

444484
It is recommended to check a user's authorization status on app start, or possibly before sensitive transactions, since these could be changed outside of your app.
445485

446-
You can simplify this Authorization-Flow by implementing the `UserRepositoryProtocol`. We provide two functions, choose the one you need according to your defined Authentication Providers.
486+
You can simplify this Authorization-Flow by implementing the `UserRepositoryProtocol`.
447487

448488
If you stored your users details in a remote database, you might want to fetch it after the authorization was successful.
449489
For this you can implement the `fetchCurrentUser`-Method, which is executed automatically on successful authorization.
@@ -453,21 +493,6 @@ class UserRepository: UserRepositoryProtocol {
453493

454494
// Custom Function for checking Authorization -> can be called anywhere in your app where you need it
455495
func checkAuthState() {
456-
// Depending on your Authentication Provider choose
457-
458-
// (a) if you support anonymous users
459-
self.checkAuthorizationWithAnonymousUser { error in
460-
if let error {
461-
// Handle Error
462-
463-
// We recommend signing out
464-
AuthenticationManager.signOut { _ in }
465-
}
466-
467-
// if no error occured fetchCurrentUser() is called automatically
468-
}
469-
470-
// (b) if you support only Sign In With Apple
471496
self.checkAuthorization { error in
472497
if let error {
473498
// Handle Error

Sources/AuthenticationManager/Authentication/SignIn.swift renamed to Sources/AuthenticationManager/Authentication/Sign In/Anonymous/Anonymous.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
//
2-
// Authentication.swift
2+
// Anonymous.swift
33
//
44
//
5-
// Created by Anna Münster on 22.09.22.
5+
// Created by Anna Münster on 20.01.23.
66
//
77

88
import Foundation
99

1010
extension AuthenticationManager {
1111
public static func authenticateAnonymously(completion: @escaping(Error?) -> Void) {
12-
guard configuration.allowAnonymousUsers else {
12+
guard configuration.authProvider.contains(.anonymous) else {
1313
completion(AuthenticationError.configuration)
1414
return
1515
}
@@ -24,10 +24,12 @@ extension AuthenticationManager {
2424
auth.signInAnonymously { _, error in
2525
if let error {
2626
completion(AuthenticationError.firebase(error: error))
27-
} else {
28-
print("Created account for anonymous user with id \(userId ?? "")")
29-
completion(nil)
27+
return
3028
}
29+
30+
print("Created account for anonymous user with id \(userId ?? "")")
31+
self.currentProvider = .anonymous
32+
completion(nil)
3133
}
3234
}
3335
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//
2+
// EmailPassword.swift
3+
//
4+
//
5+
// Created by Anna Münster on 12.01.23.
6+
//
7+
8+
import Foundation
9+
import FirebaseAuth
10+
11+
extension AuthenticationManager {
12+
13+
public static func signUpWithEmail(email: String, password: String, completion: @escaping (Error?) -> Void) {
14+
guard configuration.authProvider.contains(.emailPassword) else {
15+
completion(AuthenticationError.configuration)
16+
return
17+
}
18+
19+
auth.createUser(withEmail: email, password: password) { _, error in
20+
if let error {
21+
completion(AuthenticationError.firebase(error: error))
22+
return
23+
}
24+
25+
print("Welcome user with id \(userId ?? "")")
26+
self.currentProvider = .emailPassword
27+
completion(nil)
28+
}
29+
}
30+
31+
public static func loginWithEmail(email: String, password: String, completion: @escaping (Error?) -> Void) {
32+
guard configuration.authProvider.contains(.emailPassword) else {
33+
completion(AuthenticationError.configuration)
34+
return
35+
}
36+
37+
auth.signIn(withEmail: email, password: password) { _, error in
38+
if let error {
39+
completion(AuthenticationError.firebase(error: error))
40+
return
41+
}
42+
43+
print("Welcome user with id \(userId ?? "")")
44+
self.currentProvider = .emailPassword
45+
completion(nil)
46+
}
47+
}
48+
49+
public static func resetPassword(for email: String) {
50+
auth.sendPasswordReset(withEmail: email) { error in
51+
print("Reset mail was sent successfully to \(email)")
52+
}
53+
}
54+
55+
private static func reauthenticateWithEmail(password: String, completion: @escaping (Error?) -> Void) {
56+
guard let currentUser, let email = currentUser.email else {
57+
completion(AuthenticationError.unauthorized)
58+
return
59+
}
60+
61+
let credential = EmailAuthProvider.credential(withEmail: email, password: password)
62+
63+
currentUser.reauthenticate(with: credential) { _, error in
64+
if let error {
65+
completion(AuthenticationError.firebase(error: error))
66+
return
67+
}
68+
69+
completion(nil)
70+
}
71+
}
72+
73+
public static func updateMail(currentPassword: String, newMail: String, completion: @escaping (Error?) -> Void) {
74+
guard let currentUser else {
75+
completion(AuthenticationError.unauthorized)
76+
return
77+
}
78+
79+
reauthenticateWithEmail(password: currentPassword) { error in
80+
if let error {
81+
completion(error)
82+
return
83+
}
84+
85+
currentUser.updateEmail(to: newMail)
86+
completion(nil)
87+
}
88+
}
89+
90+
public static func updatePassword(currentPassword: String, newPassword: String, completion: @escaping (Error?) -> Void) {
91+
guard let currentUser else {
92+
completion(AuthenticationError.unauthorized)
93+
return
94+
}
95+
96+
reauthenticateWithEmail(password: currentPassword) { error in
97+
currentUser.updatePassword(to: newPassword) { error in
98+
completion(error)
99+
return
100+
}
101+
102+
completion(nil)
103+
}
104+
}
105+
106+
public static func deleteAccount(currentPassword: String, completion: @escaping (Error?) -> Void) {
107+
guard let currentUser else {
108+
completion(AuthenticationError.unauthorized)
109+
return
110+
}
111+
112+
reauthenticateWithEmail(password: currentPassword) { error in
113+
currentUser.delete { error in
114+
if let error {
115+
completion(AuthenticationError.firebase(error: error))
116+
return
117+
}
118+
119+
self.currentProvider = nil
120+
121+
completion(nil)
122+
}
123+
}
124+
}
125+
}

Sources/AuthenticationManager/Authentication/Sign in with Apple/Authorization.swift renamed to Sources/AuthenticationManager/Authentication/Sign In/Sign in with Apple/Authorization.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ extension AuthenticationManager {
2525

2626
This cannot be used to ask for permission. It is solely to check for the permission status.
2727
*/
28-
static func checkAuthorizationState(completion: @escaping (Result<Any?, AuthorizationError>) -> Void) {
28+
public static func checkAuthorizationState(completion: @escaping (Result<Bool?, Error>) -> Void) {
2929
guard let authorizationKey = authorizationKey, !authorizationKey.isEmpty else {
30-
completion(.failure(.credentialState(description: "Missing Key from User Defaults", error: nil)))
30+
completion(.failure(AuthorizationError.credentialState(description: "Missing Key from User Defaults", error: nil)))
3131
return
3232
}
3333

@@ -36,7 +36,7 @@ extension AuthenticationManager {
3636
case .authorized:
3737
completion(.success(nil))
3838
default:
39-
completion(.failure(.credentialState(description: credentialState.description, error: error)))
39+
completion(.failure(AuthorizationError.credentialState(description: credentialState.description, error: error)))
4040
}
4141
}
4242
}

Sources/AuthenticationManager/Authentication/Sign in with Apple/SignInWithApple.swift renamed to Sources/AuthenticationManager/Authentication/Sign In/Sign in with Apple/SignInWithApple.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ extension AuthenticationManager {
3737
checkAuthorizationResult(authResult) { result in
3838
switch result {
3939
case .success(let credential):
40+
self.currentProvider = .signInWithApple
4041
updateUserInfo(credential: credential, completion: completion)
4142
case .failure(let error):
4243
self.handleError(error, completion: completion)
@@ -88,13 +89,17 @@ extension AuthenticationManager {
8889
static func authenticate(credential: AuthCredential, completion: @escaping (Result<Bool, Error>) -> Void) {
8990

9091
if let currentUser {
92+
// Some user has used the app, either anonymously or with Apple
93+
9194
if !userIsAuthenticated {
9295
// anonymous account is linked to new created one
9396
currentUser.link(with: credential, completion: handleResult)
9497
} else {
98+
// Account is reauthenticated to perform some security-sensitive actions (deleting account for now, later may be password change or mail-adress-change)
9599
currentUser.reauthenticate(with: credential, completion: handleResult)
96100
}
97101
} else {
102+
// Only happens if no user has ever used the app on the device
98103
auth.signIn(with: credential, completion: handleResult)
99104
}
100105

Sources/AuthenticationManager/Authentication/SignOut.swift

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Lifecycle.swift
2+
// SignOut.swift
33
//
44
//
55
// Created by Anna Münster on 22.09.22.
@@ -10,17 +10,22 @@ import Foundation
1010
extension AuthenticationManager {
1111
/**
1212
Sign out from Firebase on the device and remove the authorization key for sign in with Apple.
13+
14+
The providerId is checked because if there is none, the user is either not signed in or is anonymous.
1315
*/
1416
public static func signOut(completion: @escaping (Error?) -> Void) {
15-
guard let providerId = auth.currentUser?.providerData.first?.providerID,
16-
self.providerId == providerId
17-
else {
18-
completion(AuthorizationError.providerId)
19-
return
17+
if userIsAuthenticated, currentProvider == .signInWithApple {
18+
guard let providerId = auth.currentUser?.providerData.first?.providerID,
19+
self.providerId == providerId
20+
else {
21+
completion(AuthorizationError.providerId)
22+
return
23+
}
2024
}
2125

2226
do {
2327
try auth.signOut()
28+
self.currentProvider = nil
2429
removeAuthorizationKey()
2530
completion(nil)
2631
} catch let signOutError {
@@ -35,8 +40,8 @@ extension AuthenticationManager {
3540
- Attention: Database Triggers need to be implemented to remove all associated data.
3641
*/
3742
public static func deleteAccount(completion: @escaping(Error?) -> Void) {
38-
guard let currentUser = auth.currentUser else {
39-
completion(nil)
43+
guard let currentUser else {
44+
completion(AuthenticationError.unauthorized)
4045
return
4146
}
4247

@@ -46,6 +51,7 @@ extension AuthenticationManager {
4651
return
4752
}
4853

54+
self.currentProvider = nil
4955
removeAuthorizationKey()
5056
completion(nil)
5157
}

0 commit comments

Comments
 (0)