Skip to content

Commit

Permalink
BIT-104: Accounts Register Request (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
jubie-livefront authored Sep 13, 2023
1 parent 888dfd6 commit a1c85ff
Show file tree
Hide file tree
Showing 12 changed files with 323 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Foundation
import Networking

// MARK: - CreateAccountRequestModel

/// The data to include in the body of a `CreateAccountRequest`.
///
struct CreateAccountRequestModel: Equatable {
// MARK: Properties

/// The captcha response used in validating a user for this request.
let captchaResponse: String? = nil

/// The user's email address.
let email: String

/// The type of kdf for this request.
let kdf: KdfType? = nil

/// The number of kdf iterations performed in this request.
let kdfIterations: Int? = nil

/// The kdf memory allocated for the computed password hash.
let kdfMemory: Int? = nil

/// The number of threads upon which the kdf iterations are performed.
let kdfParallelism: Int? = nil

/// The key used for this request.
let key: String? = nil

/// The keys used for this request.
let keys: KeysRequestModel? = nil

/// The master password hash used to authenticate a user.
let masterPasswordHash: String // swiftlint:disable:this inclusive_language

/// The master password hint.
let masterPasswordHint: String? = nil // swiftlint:disable:this inclusive_language

/// The user's name.
let name: String? = nil

/// The organization's user ID.
let organizationUserId: String? = nil

/// The token used when making this request.
let token: String? = nil
}

// MARK: JSONRequestBody

extension CreateAccountRequestModel: JSONRequestBody {
static var encoder: JSONEncoder {
JSONEncoder()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation
import Networking

// MARK: - CreateAccountResponseModel

/// The response returned from the API upon creating an account.
///
struct CreateAccountResponseModel: JSONResponse {
static var decoder = JSONDecoder()

// MARK: Properties

/// The captcha bypass token returned in this response.
var captchaBypassToken: String?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import XCTest

@testable import BitwardenShared

// MARK: - CreateAccountResponseModelTests

class CreateAccountResponseModelTests: BitwardenTestCase {
/// Tests that a response is initialized correctly.
func test_init() {
let subject = CreateAccountResponseModel(captchaBypassToken: "captchaBypassToken")
XCTAssertEqual(subject.captchaBypassToken, "captchaBypassToken")
}

/// Tests the successful decoding of a JSON response.
func test_decode_success() throws {
let json = APITestData.createAccountResponse.data
let decoder = JSONDecoder()
let subject = try decoder.decode(CreateAccountResponseModel.self, from: json)
XCTAssertEqual(subject.captchaBypassToken, "captchaBypassToken")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
extension APITestData {
static let createAccountRequest = loadFromBundle(resource: "Request", extension: "json")
static let createAccountResponse = loadFromBundle(resource: "Success", extension: "json")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "name",
"email": "email",
"masterPasswordHash": "masterPasswordHash",
"masterPasswordHint": "masterPasswordHint",
"captchaResponse": "captchaResponse",
"key": "key",
"keys": {
"publicKey": "publicKey",
"encryptedPrivateKey": "encryptedPrivateKey"
},
"token": "token",
"organizationUserId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kdf": 0,
"kdfIterations": 0,
"kdfMemory": 0,
"kdfParallelism": 0,
"referenceData": {
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"object": "object",
"captchaBypassToken": "captchaBypassToken"
}
11 changes: 11 additions & 0 deletions BitwardenShared/Core/Auth/Models/API/CreateAccount/KdfType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// MARK: - KdfType

/// The type of key derivation function.
///
enum KdfType: Int, Codable, Equatable {
/// The PBKDF2 SHA256 type.
case pbkdf2sha256 = 0

/// The Argon2id type.
case argon2id = 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// MARK: - KeysRequestModel

/// A model for keys used in the `CreateAccountRequest`.
///
struct KeysRequestModel: Codable, Equatable {
// MARK: Properties

/// The public key used in a `CreateAccountRequest`.
var publicKey: String?

/// The encrypted private key used in a `CreateAccountRequest`.
let encryptedPrivateKey: String
}
21 changes: 19 additions & 2 deletions BitwardenShared/Core/Auth/Services/API/AccountAPIService.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
// MARK: - AccountAPIService

/// A protocol for an API service used to make account requests.
///
protocol AccountAPIService {}
protocol AccountAPIService {
/// Creates an API call for when the user submits an account creation form.
///
/// - Parameter body: The body to be included in the request.
///
/// - Returns data returned from the `CreateAccountRequest`.
///
func createNewAccount(body: CreateAccountRequestModel) async throws -> CreateAccountResponseModel
}

// MARK: - APIService

extension APIService: AccountAPIService {}
extension APIService: AccountAPIService {
func createNewAccount(body: CreateAccountRequestModel) async throws -> CreateAccountResponseModel {
let request = CreateAccountRequest(body: body)
return try await apiService.send(request)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import XCTest

@testable import BitwardenShared

// MARK: - AccountAPIServiceTests

class AccountAPIServiceTests: BitwardenTestCase {
// MARK: Properties

var client: MockHTTPClient!
var subject: APIService!

override func setUp() {
super.setUp()
client = MockHTTPClient()
subject = APIService(client: client)
}

override func tearDown() {
super.tearDown()
client = nil
subject = nil
}

// MARK: Account creation

/// `createNewAccount(email:masterPasswordHash)` throws an error if the request fails.
func test_create_account_httpFailure() async {
client.result = .httpFailure()

await assertAsyncThrows {
_ = try await subject.createNewAccount(
body: CreateAccountRequestModel(
email: "[email protected]",
masterPasswordHash: "1234"
)
)
}
}

/// `createNewAccount(email:masterPasswordHash)` throws a decoding error if the response is not the expected type.
func test_create_account_failure() async throws {
let resultData = APITestData(data: Data("this should fail".utf8))
client.result = .httpSuccess(testData: resultData)

await assertAsyncThrows {
_ = try await subject.createNewAccount(
body: CreateAccountRequestModel(
email: "[email protected]",
masterPasswordHash: "1234"
)
)
}
}

/// `createNewAccount(email:masterPasswordHash)` returns the correct value from the API with a successful request.
func test_create_account_success() async throws {
let resultData = APITestData.createAccountResponse
client.result = .httpSuccess(testData: resultData)

let successfulResponse = try await subject.createNewAccount(
body: CreateAccountRequestModel(
email: "[email protected]",
masterPasswordHash: "1234"
)
)

let request = try XCTUnwrap(client.requests.first)
XCTAssertEqual(request.method, .post)
XCTAssertEqual(request.url.relativePath, "/api/accounts/register")
XCTAssertEqual(successfulResponse.captchaBypassToken, "captchaBypassToken")
XCTAssertNotNil(request.body)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation
import Networking

// MARK: - CreateAccountRequest

/// The API request sent when submitting an account creation form.
///
struct CreateAccountRequest: Request {
typealias Response = CreateAccountResponseModel
typealias Body = CreateAccountRequestModel

/// The body of this request.
var body: CreateAccountRequestModel?

/// The HTTP method for this request.
let method: HTTPMethod = .post

/// The URL path for this request.
var path: String = "/accounts/register"

/// Creates a new `CreateAccountRequest` instance.
///
/// - Parameter body: The body of the request.
///
init(body: CreateAccountRequestModel) {
self.body = body
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import XCTest

@testable import BitwardenShared

// MARK: - CreateAccountRequestTests

class CreateAccountRequestTests: BitwardenTestCase {
/// Validate that the method is correct.
func test_method() {
let subject = CreateAccountRequest(
body: CreateAccountRequestModel(
email: "[email protected]",
masterPasswordHash: "1234"
)
)
XCTAssertEqual(subject.method, .post)
}

/// Validate that the path is correct.
func test_path() {
let subject = CreateAccountRequest(
body: CreateAccountRequestModel(
email: "[email protected]",
masterPasswordHash: "1234"
)
)
XCTAssertEqual(subject.path, "/accounts/register")
}

/// Validate that the body is not nil.
func test_body() {
let subject = CreateAccountRequest(
body: CreateAccountRequestModel(
email: "[email protected]",
masterPasswordHash: "1234"
)
)
XCTAssertNotNil(subject.body)
}

// MARK: Init

/// Validate that the value provided to the init method is correct.
func test_init_body() {
let subject = CreateAccountRequest(
body: CreateAccountRequestModel(
email: "[email protected]",
masterPasswordHash: "1234"
)
)
XCTAssertEqual(subject.body?.email, "[email protected]")
XCTAssertEqual(subject.body?.masterPasswordHash, "1234")
}
}

0 comments on commit a1c85ff

Please sign in to comment.