Skip to content

Commit 046e7fb

Browse files
Migrate to ApodiniAuthorization (#1)
* * Migrate to ApodiniAuthorization * Migrate to Metadata DSL for specifying Operation * Fix swiftlint * Add Contributor note * Incorporate latest changes of Apodini (@Authorized property wrapper)
1 parent 90368df commit 046e7fb

25 files changed

+183
-106
lines changed

CONTRIBUTORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ Apodini Xpense Example contributors
66
* [Lara Marie Reimer](...)
77
* [Dominic Henze](...)
88
* [Florian Bodlée](...)
9+
* [Andreas Bauer](https://github.com/Supereg)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ To start and test the web service, you can run the `$ docker compose up` command
1212

1313
Xcode 13 (only available on macOS) is required to build and run the example client application. Follow the instructions on https://developer.apple.com/xcode/ to install the latest version of Xcode.
1414

15-
1. Opening the *Xoense.xcworkspace*. The workspace bundles the web services and the client application.
15+
1. Opening the *Xpense.xcworkspace*. The workspace bundles the web services and the client application.
1616
2. Select the *WebService* target, and then the *Xpense* target and start the web service as well as the app by following the instructions on [Running Your App in the Simulator or on a Device](https://developer.apple.com/documentation/xcode/running-your-app-in-the-simulator-or-on-a-device)
1717

1818
## System Functionality

Shared/Sources/XpenseModel/Model.swift

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,28 @@ open class Model {
195195
userDidSet()
196196
return user
197197
}
198+
199+
/// Verifies the credentials of a given username-password combination.
200+
/// - Parameters:
201+
/// - name: The name of the ``User`` that is used to authenticate the ``User``
202+
/// - password: The password of the ``User`` that is used to authenticate the ``User``
203+
/// - Returns: The ``User`` for the given username, if the password check succeeded.
204+
open func verifyCredentials(_ name: String, password: String) async throws -> User {
205+
guard let user = users.first(where: { $0.name == name }), user.verify(password: password) else {
206+
throw XpenseServiceError.loginFailed
207+
}
208+
return user
209+
}
210+
211+
/// Creates a new login token for a given ``User`` instance.
212+
/// - Parameter user: The ``User`` instance for which a new authentication token shall be created.
213+
/// - Returns: Returns the freshly created auth token.
214+
open func createToken(for user: inout User) async -> String {
215+
let token = user.createToken()
216+
users.update(with: user)
217+
userDidSet()
218+
return token
219+
}
198220

199221
/// Provides the login functionality of the `Model`
200222
/// - Parameters:
@@ -203,13 +225,8 @@ open class Model {
203225
/// - Returns: The token that can be used to further authenticate requests to the Xpense Web Service
204226
@discardableResult
205227
open func login(_ name: String, password: String) async throws -> String {
206-
guard var user = users.first(where: { $0.name == name }), user.verify(password: password) else {
207-
throw XpenseServiceError.loginFailed
208-
}
209-
let token = user.createToken()
210-
users.update(with: user)
211-
userDidSet()
212-
return token
228+
var user = try await verifyCredentials(name, password: password)
229+
return await createToken(for: &user)
213230
}
214231

215232
/// Logout the current `User` that is signed in and remove all personal data of the `User` stored in this `Model`

WebService/Package.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ let package = Package(
1212
.executable(name: "WebService", targets: ["WebService"])
1313
],
1414
dependencies: [
15-
.package(url: "https://github.com/Apodini/Apodini.git", .revision("251bbba60b1023443b6973d80ba42c4155fe143a")),
15+
.package(url: "https://github.com/Apodini/Apodini.git", .revision("d68e398166acc43308fecd5ed0529a7bca66dc9e")),
1616
.package(name: "Shared", path: "../Shared")
1717
],
1818
targets: [
@@ -22,6 +22,9 @@ let package = Package(
2222
.product(name: "Apodini", package: "Apodini"),
2323
.product(name: "ApodiniREST", package: "Apodini"),
2424
.product(name: "ApodiniOpenAPI", package: "Apodini"),
25+
.product(name: "ApodiniAuthorization", package: "Apodini"),
26+
.product(name: "ApodiniAuthorizationBasicScheme", package: "Apodini"),
27+
.product(name: "ApodiniAuthorizationBearerScheme", package: "Apodini"),
2528
.product(name: "XpenseModel", package: "Shared")
2629
]
2730
)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// This source file is part of the Apodini Xpense Example
3+
//
4+
// SPDX-FileCopyrightText: 2018-2021 Paul Schmiedmayer and project authors (see CONTRIBUTORS.md) <[email protected]>
5+
//
6+
// SPDX-License-Identifier: MIT
7+
//
8+
9+
import Apodini
10+
import ApodiniAuthorization
11+
import XpenseModel
12+
13+
struct UserCredentialsVerifier: AuthenticationVerifier {
14+
@Environment(\.xpenseModel) var xpenseModel
15+
16+
@Throws(.unauthenticated, reason: "The provided credentials are not correct")
17+
var unauthenticatedError: ApodiniError
18+
19+
typealias AuthenticationInfo = (username: String, password: String)
20+
21+
func initializeAndVerify(for authenticationInfo: AuthenticationInfo) async throws -> User {
22+
do {
23+
return try await xpenseModel.verifyCredentials(authenticationInfo.username, password: authenticationInfo.password)
24+
} catch {
25+
throw unauthenticatedError
26+
}
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// This source file is part of the Apodini Xpense Example
3+
//
4+
// SPDX-FileCopyrightText: 2018-2021 Paul Schmiedmayer and project authors (see CONTRIBUTORS.md) <[email protected]>
5+
//
6+
// SPDX-License-Identifier: MIT
7+
//
8+
9+
import Apodini
10+
import ApodiniAuthorization
11+
import XpenseModel
12+
13+
struct UserTokenVerifier: AuthenticationVerifier {
14+
@Environment(\.xpenseModel) var xpenseModel
15+
16+
@Throws(.unauthenticated,
17+
reason: "The user could not be authenticated",
18+
description: "Could not find a user for the supplied token!")
19+
var tokenNotFound: ApodiniError
20+
21+
func initializeAndVerify(for authenticationInfo: String) throws -> User {
22+
guard let user = xpenseModel.user(forToken: authenticationInfo) else {
23+
throw tokenNotFound
24+
}
25+
26+
return user
27+
}
28+
}

WebService/Sources/WebService/Components/AccountComponent.swift

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,19 @@
88

99
import Apodini
1010
import Foundation
11-
import XpenseModel
12-
1311

1412
struct AccountComponent: Component {
1513
@PathParameter var accountId: UUID
16-
17-
14+
1815
var content: some Component {
1916
Group("accounts") {
2017
GetAllAccounts()
2118
CreateAccount()
22-
.operation(.create)
19+
2320
Group($accountId) {
2421
GetAccount(id: $accountId)
2522
UpdateAccount(id: $accountId)
26-
.operation(.update)
2723
DeleteAccount(id: $accountId)
28-
.operation(.delete)
2924
}
3025
}
3126
}

WebService/Sources/WebService/Components/AccountHandlers/CreateAccount.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//
88

99
import Apodini
10+
import ApodiniAuthorization
1011
import XpenseModel
1112

1213

@@ -17,18 +18,18 @@ struct CreateAccount: Handler {
1718

1819

1920
@Environment(\.xpenseModel) var xpenseModel
20-
@Environment(\.connection) var connection
2121

2222
@Parameter(.http(.body)) var account: CreateAccountMediator
2323

24-
@Throws(.unauthenticated, reason: "The User is not Authenticated correctly") var userNotFound: ApodiniError
25-
24+
@Authorized(User.self) var user
2625

2726
func handle() async throws -> Account {
28-
guard let user = xpenseModel.user(fromConnection: connection) else {
29-
throw userNotFound
30-
}
27+
let user = try user()
3128

3229
return try await xpenseModel.save(Account(name: account.name, userID: user.id))
3330
}
31+
32+
var metadata: Metadata {
33+
Operation(.create)
34+
}
3435
}

WebService/Sources/WebService/Components/AccountHandlers/DeleteAccount.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,22 @@
77
//
88

99
import Apodini
10+
import ApodiniAuthorization
1011
import Foundation
1112
import XpenseModel
1213

1314

1415
struct DeleteAccount: Handler {
1516
@Environment(\.xpenseModel) var xpenseModel
16-
@Environment(\.connection) var connection
1717

1818
@Binding var id: UUID
1919

20-
@Throws(.unauthenticated, reason: "The User is not Authenticated correctly") var userNotFound: ApodiniError
2120
@Throws(.notFound, reason: "The Account could not be found") var notFound: ApodiniError
2221

22+
@Authorized(User.self) var user
2323

2424
func handle() async throws -> Status {
25-
guard let user = xpenseModel.user(fromConnection: connection) else {
26-
throw userNotFound
27-
}
25+
let user = try user()
2826

2927
guard let account = xpenseModel.account(id),
3028
account.userID == user.id else {
@@ -35,4 +33,8 @@ struct DeleteAccount: Handler {
3533

3634
return .noContent
3735
}
36+
37+
var metadata: Metadata {
38+
Operation(.delete)
39+
}
3840
}

WebService/Sources/WebService/Components/AccountHandlers/GetAccount.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,22 @@
77
//
88

99
import Apodini
10+
import ApodiniAuthorization
1011
import Foundation
1112
import XpenseModel
1213

1314

1415
struct GetAccount: Handler {
1516
@Environment(\.xpenseModel) var xpenseModel
16-
@Environment(\.connection) var connection
1717

1818
@Binding var id: UUID
1919

20-
@Throws(.unauthenticated, reason: "The User is not Authenticated correctly") var userNotFound: ApodiniError
2120
@Throws(.notFound, reason: "The Account could not be found") var notFound: ApodiniError
2221

22+
@Authorized(User.self) var user
2323

2424
func handle() throws -> Account {
25-
guard let user = xpenseModel.user(fromConnection: connection) else {
26-
throw userNotFound
27-
}
25+
let user = try user()
2826

2927
guard let account = xpenseModel.account(id), account.userID == user.id else {
3028
throw notFound

WebService/Sources/WebService/Components/AccountHandlers/GetAllAccounts.swift

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,17 @@
77
//
88

99
import Apodini
10+
import ApodiniAuthorization
1011
import XpenseModel
1112

1213

1314
struct GetAllAccounts: Handler {
1415
@Environment(\.xpenseModel) var xpenseModel
15-
@Environment(\.connection) var connection
16-
17-
@Throws(.unauthenticated, reason: "The User is not Authenticated correctly") var userNotFound: ApodiniError
18-
16+
17+
@Authorized(User.self) var user
1918

2019
func handle() throws -> [Account] {
21-
guard let user = xpenseModel.user(fromConnection: connection) else {
22-
throw userNotFound
23-
}
24-
20+
let user = try user()
2521
return user.accounts(xpenseModel)
2622
}
2723
}

WebService/Sources/WebService/Components/AccountHandlers/UpdateAccount.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//
88

99
import Apodini
10+
import ApodiniAuthorization
1011
import Foundation
1112
import XpenseModel
1213

@@ -18,25 +19,26 @@ struct UpdateAccount: Handler {
1819

1920

2021
@Environment(\.xpenseModel) var xpenseModel
21-
@Environment(\.connection) var connection
2222

2323
@Binding var id: UUID
2424

2525
@Parameter(.http(.body)) var updatedAccount: UpdateAccountMediator
2626

27-
@Throws(.unauthenticated, reason: "The User is not Authenticated correctly") var userNotFound: ApodiniError
2827
@Throws(.notFound, reason: "The Account could not be found") var notFound: ApodiniError
2928

29+
@Authorized(User.self) var user
3030

3131
func handle() async throws -> Account {
32-
guard let user = xpenseModel.user(fromConnection: connection) else {
33-
throw userNotFound
34-
}
32+
let user = try user()
3533

3634
guard xpenseModel.account(id)?.userID == user.id else {
3735
throw notFound
3836
}
3937

4038
return try await xpenseModel.save(Account(id: id, name: updatedAccount.name, userID: user.id))
4139
}
40+
41+
var metadata: Metadata {
42+
Operation(.update)
43+
}
4244
}

WebService/Sources/WebService/Components/TransactionComponent.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,15 @@ import Foundation
1313
struct TransactionComponent: Component {
1414
@PathParameter var transactionId: UUID
1515

16-
1716
var content: some Component {
1817
Group("transactions") {
1918
CreateTransaction()
20-
.operation(.create)
2119
GetAllTransactions()
20+
2221
Group($transactionId) {
2322
GetTransaction(transactionId: $transactionId)
2423
UpdateTransaction(transactionId: $transactionId)
25-
.operation(.update)
2624
DeleteTransaction(transactionId: $transactionId)
27-
.operation(.delete)
2825
}
2926
}
3027
}

WebService/Sources/WebService/Components/TransactionHandlers/CreateTransaction.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//
88

99
import Apodini
10+
import ApodiniAuthorization
1011
import Foundation
1112
import XpenseModel
1213

@@ -22,18 +23,15 @@ struct CreateTransaction: Handler {
2223

2324

2425
@Environment(\.xpenseModel) var xpenseModel
25-
@Environment(\.connection) var connection
2626

2727
@Parameter(.http(.body)) var transaction: CreateTransactionMediator
2828

29-
@Throws(.unauthenticated, reason: "The User is not Authenticated correctly") var userNotFound: ApodiniError
3029
@Throws(.notFound, reason: "The Account could not be found") var notFound: ApodiniError
3130

31+
@Authorized(User.self) var user
3232

3333
func handle() async throws -> Transaction {
34-
guard let user = xpenseModel.user(fromConnection: connection) else {
35-
throw userNotFound
36-
}
34+
let user = try user()
3735

3836
guard xpenseModel.account(transaction.account)?.userID == user.id else {
3937
throw notFound
@@ -49,4 +47,8 @@ struct CreateTransaction: Handler {
4947
)
5048
)
5149
}
50+
51+
var metadata: Metadata {
52+
Operation(.create)
53+
}
5254
}

0 commit comments

Comments
 (0)