Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BIT-104: Accounts Register Request #23

Merged
merged 5 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
}
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?
fedemkr marked this conversation as resolved.
Show resolved Hide resolved

/// 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")
}
}