Skip to content

Commit

Permalink
Add reset and update password for end users (#120)
Browse files Browse the repository at this point in the history
  • Loading branch information
mederic-p committed Mar 15, 2019
1 parent 831ef9f commit 0da47db
Show file tree
Hide file tree
Showing 12 changed files with 309 additions and 2 deletions.
8 changes: 8 additions & 0 deletions OmiseGO.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@
032CEF24211D685600E44445 /* secret.plist in Resources */ = {isa = PBXBuildFile; fileRef = 032CEF22211D685600E44445 /* secret.plist */; };
032CEF2A211D783D00E44445 /* LiveTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032CEF29211D783D00E44445 /* LiveTestCase.swift */; };
033802AB215B9D2500C2501F /* AccountGetParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033802AA215B9D2500C2501F /* AccountGetParams.swift */; };
033D1889223A0E6B00B6B8D9 /* UserResetPasswordParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D1888223A0E6B00B6B8D9 /* UserResetPasswordParams.swift */; };
033D188B223A0E8000B6B8D9 /* UserUpdatePasswordParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D188A223A0E8000B6B8D9 /* UserUpdatePasswordParams.swift */; };
0341BCA2216B395D001928FE /* TransactionRequestParams+Admin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0341BCA1216B395D001928FE /* TransactionRequestParams+Admin.swift */; };
0341BCA4216B3DC5001928FE /* TransactionConsumptionParams+Admin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0341BCA3216B3DC5001928FE /* TransactionConsumptionParams+Admin.swift */; };
0341BCA7216B4024001928FE /* AdminEncodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0341BCA6216B4024001928FE /* AdminEncodeTests.swift */; };
Expand Down Expand Up @@ -302,6 +304,8 @@
032CEF22211D685600E44445 /* secret.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = secret.plist; sourceTree = "<group>"; };
032CEF29211D783D00E44445 /* LiveTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTestCase.swift; sourceTree = "<group>"; };
033802AA215B9D2500C2501F /* AccountGetParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountGetParams.swift; sourceTree = "<group>"; };
033D1888223A0E6B00B6B8D9 /* UserResetPasswordParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserResetPasswordParams.swift; sourceTree = "<group>"; };
033D188A223A0E8000B6B8D9 /* UserUpdatePasswordParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserUpdatePasswordParams.swift; sourceTree = "<group>"; };
0341BCA1216B395D001928FE /* TransactionRequestParams+Admin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransactionRequestParams+Admin.swift"; sourceTree = "<group>"; };
0341BCA3216B3DC5001928FE /* TransactionConsumptionParams+Admin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransactionConsumptionParams+Admin.swift"; sourceTree = "<group>"; };
0341BCA6216B4024001928FE /* AdminEncodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminEncodeTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -439,6 +443,8 @@
0320EAB92119499B0006685C /* TransactionRequest+Client.swift */,
0306DC9E215A2E0200612953 /* TransactionListParams.swift */,
0380AAE8212D2D78006B2193 /* SignupParams.swift */,
033D1888223A0E6B00B6B8D9 /* UserResetPasswordParams.swift */,
033D188A223A0E8000B6B8D9 /* UserUpdatePasswordParams.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -1169,6 +1175,7 @@
033802AB215B9D2500C2501F /* AccountGetParams.swift in Sources */,
0379E7F721184BC900E65D4F /* TransactionParams.swift in Sources */,
0379E80621184BC900E65D4F /* Account.swift in Sources */,
033D188B223A0E8000B6B8D9 /* UserUpdatePasswordParams.swift in Sources */,
034F1523214FA205002BB513 /* SortParams.swift in Sources */,
0379E80B21184BC900E65D4F /* Listable.swift in Sources */,
0379E81321184BC900E65D4F /* QRScannerViewController.swift in Sources */,
Expand Down Expand Up @@ -1238,6 +1245,7 @@
0341BCAC216B4316001928FE /* TransactionRequest+Admin.swift in Sources */,
03E72EDB2124177E0060E1D7 /* LoginParams.swift in Sources */,
034F1529214FA6FB002BB513 /* PaginatedListParams.swift in Sources */,
033D1889223A0E6B00B6B8D9 /* UserResetPasswordParams.swift in Sources */,
03E565B5211AFBB100BC9124 /* QRScannerViewController+Client.swift in Sources */,
034F1527214FA228002BB513 /* PaginationParams.swift in Sources */,
0379E81221184BC900E65D4F /* QRScannerLoadingView.swift in Sources */,
Expand Down
14 changes: 12 additions & 2 deletions Source/Client/API/APIClientEndpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ enum APIClientEndpoint: APIEndpoint {
case transactionConsumptionCancel(params: TransactionConsumptionCancellationParams)
case transactionConsumptionApprove(params: TransactionConsumptionConfirmationParams)
case transactionConsumptionReject(params: TransactionConsumptionConfirmationParams)
case resetPassword(params: UserResetPasswordParams)
case updatePassword(params: UserUpdatePasswordParams)
case signup(params: SignupParams)
case login(params: LoginParams)
case logout
Expand Down Expand Up @@ -47,10 +49,14 @@ enum APIClientEndpoint: APIEndpoint {
return "/me.approve_transaction_consumption"
case .transactionConsumptionReject:
return "/me.reject_transaction_consumption"
case .resetPassword:
return "/user.reset_password"
case .updatePassword:
return "/user.update_password"
case .signup:
return "user.signup"
return "/user.signup"
case .login:
return "user.login"
return "/user.login"
case .logout:
return "/me.logout"
}
Expand All @@ -74,6 +80,10 @@ enum APIClientEndpoint: APIEndpoint {
return .requestParameters(parameters: parameters)
case let .transactionConsumptionReject(parameters):
return .requestParameters(parameters: parameters)
case let .resetPassword(parameters):
return .requestParameters(parameters: parameters)
case let .updatePassword(parameters):
return .requestParameters(parameters: parameters)
case let .signup(parameters):
return .requestParameters(parameters: parameters)
case let .login(parameters):
Expand Down
34 changes: 34 additions & 0 deletions Source/Client/Models/User+Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,38 @@ extension User: Retrievable {
callback: @escaping User.RetrieveRequestCallback) -> User.RetrieveRequest? {
return self.retrieve(using: client, endpoint: APIClientEndpoint.getCurrentUser, callback: callback)
}

@discardableResult
/// Request to reset the user's password
///
/// - Parameters:
/// - client: An API client.
/// This client need to be initialized with a ClientConfiguration struct before being used.
/// - params: The UserResetPasswordParams object to use to perform the request
/// - callback: The closure called when the request is completed
/// - Returns: An optional cancellable request.
public static func resetPassword(using client: HTTPClientAPI,
params: UserResetPasswordParams,
callback: @escaping Request<EmptyResponse>.Callback) -> Request<EmptyResponse>? {
let request: Request<EmptyResponse>? =
client.request(toEndpoint: APIClientEndpoint.resetPassword(params: params), callback: callback)
return request
}

@discardableResult
/// Update the password of a user given a valid token obtained following a reset password email link
///
/// - Parameters:
/// - client: An API client.
/// This client need to be initialized with a ClientConfiguration struct before being used.
/// - params: The UserUpdatePasswordParams object to use to perform the request
/// - callback: The closure called when the request is completed
/// - Returns: An optional cancellable request.
public static func updatePassword(using client: HTTPClientAPI,
params: UserUpdatePasswordParams,
callback: @escaping Request<EmptyResponse>.Callback) -> Request<EmptyResponse>? {
let request: Request<EmptyResponse>? =
client.request(toEndpoint: APIClientEndpoint.updatePassword(params: params), callback: callback)
return request
}
}
39 changes: 39 additions & 0 deletions Source/Client/Models/UserResetPasswordParams.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// UserResetPasswordParams.swift
// OmiseGO
//
// Created by Mederic Petit on 14/3/19.
// Copyright © 2019 Omise Go Pte. Ltd. All rights reserved.
//

/// Represents a structure used to request a password reset for the user
public struct UserResetPasswordParams {
/// The email of the user
public let email: String
/// The URL where the user will be taken when clicking the link in the email
public let redirectUrl: String

/// Initialize the params used to request a password reset for the user
///
/// - Parameters:
/// - email: The email of the user
/// - redirectUrl: The URL where the user will be taken when clicking the link in the email
public init(email: String,
redirectUrl: String) {
self.email = email
self.redirectUrl = redirectUrl
}
}

extension UserResetPasswordParams: APIParameters {
private enum CodingKeys: String, CodingKey {
case email
case redirectUrl = "redirect_url"
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(email, forKey: .email)
try container.encode(redirectUrl, forKey: .redirectUrl)
}
}
53 changes: 53 additions & 0 deletions Source/Client/Models/UserUpdatePasswordParams.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// UserUpdatePasswordParams.swift
// OmiseGO
//
// Created by Mederic Petit on 14/3/19.
// Copyright © 2019 Omise Go Pte. Ltd. All rights reserved.
//

/// Represents a structure used to update the password of a user following a reset
public struct UserUpdatePasswordParams {
/// The email of the user (obtained from the params in the link sent to the email of the user)
public let email: String
/// The unique reset password token obtained from the params in the link sent to the email of the user
public let token: String?
/// The updated password
public let password: String
/// The password confirmation that should match the updated password
public let passwordConfirmation: String

/// Initialize the params used to signup a user
///
/// - Parameters:
/// - email: The email of the user (obtained from the params in the link sent to the email of the user)
/// - token: The unique reset password token obtained from the params in the link sent to the email of the user
/// - password: The updated password
/// - passwordConfirmation: The password confirmation that should match the updated password
public init(email: String,
token: String,
password: String,
passwordConfirmation: String) {
self.email = email
self.token = token
self.password = password
self.passwordConfirmation = passwordConfirmation
}
}

extension UserUpdatePasswordParams: APIParameters {
private enum CodingKeys: String, CodingKey {
case email
case token
case password
case passwordConfirmation = "password_confirmation"
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(email, forKey: .email)
try container.encode(token, forKey: .token)
try container.encode(password, forKey: .password)
try container.encode(passwordConfirmation, forKey: .passwordConfirmation)
}
}
14 changes: 14 additions & 0 deletions Tests/Client/APITests/APIClientEndpointTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class APIClientEndpointTest: XCTestCase {
let validTransactionCreateParams = StubGenerator.transactionRequestCreateParams()
let validTransactionGetParams = StubGenerator.transactionRequestGetParams()
let validTransactionListParams = StubGenerator.transactionListParams()
let validResetPasswordParams = StubGenerator.resetPasswordParams()
let validUpdatePasswordParams = StubGenerator.updatePasswordParams()

func testPath() {
XCTAssertEqual(APIClientEndpoint.getCurrentUser.path, "/me.get")
Expand All @@ -27,6 +29,10 @@ class APIClientEndpointTest: XCTestCase {
"/me.get_transaction_request")
XCTAssertEqual(APIClientEndpoint.transactionRequestConsume(params: self.validTransactionConsumptionParams).path,
"/me.consume_transaction_request")
XCTAssertEqual(APIClientEndpoint.resetPassword(params: self.validResetPasswordParams).path,
"/user.reset_password")
XCTAssertEqual(APIClientEndpoint.updatePassword(params: self.validUpdatePasswordParams).path,
"/user.update_password")
XCTAssertEqual(APIClientEndpoint.logout.path, "/me.logout")
}

Expand Down Expand Up @@ -59,6 +65,14 @@ class APIClientEndpointTest: XCTestCase {
case .requestParameters: break
default: XCTFail("Wrong task")
}
switch APIClientEndpoint.resetPassword(params: self.validResetPasswordParams).task {
case .requestParameters: break
default: XCTFail("Wrong task")
}
switch APIClientEndpoint.updatePassword(params: self.validUpdatePasswordParams).task {
case .requestParameters: break
default: XCTFail("Wrong task")
}
switch APIClientEndpoint.transactionRequestConsume(params: self.validTransactionConsumptionParams).task {
case .requestParameters: break
default: XCTFail("Wrong task")
Expand Down
30 changes: 30 additions & 0 deletions Tests/Client/FixtureTests/UserFixtureTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,34 @@ class UserFixtureTests: FixtureClientTestCase {
XCTAssertNotNil(request)
waitForExpectations(timeout: 15.0, handler: nil)
}

func testResetPassword() {
let expectation = self.expectation(description: "Reset password")
let params = StubGenerator.resetPasswordParams()
let request = User.resetPassword(using: self.testClient, params: params) { result in
switch result {
case let .fail(error: error):
XCTFail("\(error)")
case .success: break
}
expectation.fulfill()
}
XCTAssertNotNil(request)
waitForExpectations(timeout: 15.0, handler: nil)
}

func testUpdatePassword() {
let expectation = self.expectation(description: "Update password")
let params = StubGenerator.updatePasswordParams()
let request = User.updatePassword(using: self.testClient, params: params) { result in
switch result {
case let .fail(error: error):
XCTFail("\(error)")
case .success: break
}
expectation.fulfill()
}
XCTAssertNotNil(request)
waitForExpectations(timeout: 15.0, handler: nil)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"version": "1",
"success": true,
"data": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"version": "1",
"success": true,
"data": {}
}
36 changes: 36 additions & 0 deletions Tests/Core/CodingTests/EncodeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -657,4 +657,40 @@ class EncodeTests: XCTestCase {
XCTFail(thrownError.localizedDescription)
}
}

func testResetPasswordParamsEncoding() {
do {
let resetPasswordParams = UserResetPasswordParams(email: "email@example.com",
redirectUrl: "xxx")
let encodedData = try self.encoder.encode(resetPasswordParams)
XCTAssertEqual(String(data: encodedData, encoding: .utf8)!, """
{
"email":"email@example.com",
"redirect_url":"xxx"
}
""".uglifiedEncodedString())
} catch let thrownError {
XCTFail(thrownError.localizedDescription)
}
}

func testUpdatePasswordParamsEncoding() {
do {
let updatePassword = UserUpdatePasswordParams(email: "email@example.com",
token: "xxx",
password: "password",
passwordConfirmation: "password")
let encodedData = try self.encoder.encode(updatePassword)
XCTAssertEqual(String(data: encodedData, encoding: .utf8)!, """
{
"email":"email@example.com",
"password":"password",
"password_confirmation":"password",
"token":"xxx"
}
""".uglifiedEncodedString())
} catch let thrownError {
XCTFail(thrownError.localizedDescription)
}
}
}
17 changes: 17 additions & 0 deletions Tests/Core/Helpers/StubGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -489,4 +489,21 @@ class StubGenerator {
createdAt: createdAt ?? v.createdAt,
updatedAt: updatedAt ?? v.updatedAt)
}

class func resetPasswordParams(
email: String = "email@example.com",
redirectURL: String = "http://localhost:4000") -> UserResetPasswordParams {
return UserResetPasswordParams(email: email, redirectUrl: redirectURL)
}

class func updatePasswordParams(
email: String = "email@example.com",
token: String = "XXXXXXXXXXXXXXX",
password: String = "password",
passwordConfirmation _: String = "password") -> UserUpdatePasswordParams {
return UserUpdatePasswordParams(email: email,
token: token,
password: password,
passwordConfirmation: password)
}
}

0 comments on commit 0da47db

Please sign in to comment.