Skip to content

Commit

Permalink
Adds manual OTP entry functionality and updates tests, bumps version …
Browse files Browse the repository at this point in the history
…to 1.1.0
  • Loading branch information
bakatz committed Sep 21, 2023
1 parent b973b3e commit 5740bb9
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 22 deletions.
88 changes: 83 additions & 5 deletions __tests__/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,84 @@ test('signIn() returns a networking error when the http call does not have a res
expect(resp.message).toBe('Could not connect to the server. Try again in a few moments.')
})

test('auth() updates localStorage with the user details when the http call succeeds', async() => {
const simpleOTP = new SimpleOTP('mocksiteid')
const mockPost = vi.spyOn(http, 'post')
mockPost.mockImplementation(() => {
return { data: { data: { id: 'someid', email: '[email protected]', token: 'reallysecuretoken' } } }
})

const authResponse = await simpleOTP.auth('reallysecurecode')
expect(authResponse).toBeTruthy()
expect(authResponse.data).toBeTruthy()
expect(authResponse.data.email).toBe('[email protected]')
expect(authResponse.data.token).toBe('reallysecuretoken')

// Make sure the user saved to local storage has the same props as the user returned above
expect(simpleOTP.getUser().email).toBe(authResponse.data.email)
expect(simpleOTP.getUser().token).toBe(authResponse.data.token)
expect(simpleOTP.isAuthenticated()).toBe(true)
})

test('auth() throws when the code is missing', async() => {
const simpleOTP = new SimpleOTP('mocksiteid')
const mockPost = vi.spyOn(http, 'post')
simpleOTP.signOut()
mockPost.mockImplementation(() => {
return { data: { data: { id: 'someid', email: '[email protected]', token: 'reallysecuretoken' } } }
})

expect(async () => await simpleOTP.auth()).rejects.toThrow()
expect(simpleOTP.getUser()).toBeNull()
})

test('auth() returns the error code in the response when the http call returns an error response', async() => {
const simpleOTP = new SimpleOTP('mocksiteid')
const mockPost = vi.spyOn(http, 'post')
mockPost.mockImplementation(() => {
throw {
response: {
data: {
code: 'invalid_auth_code',
message: 'bad auth code',
data: { id: 'someid', email: '[email protected]', token: 'reallysecuretoken' }
}
}
}
})


const res = await simpleOTP.auth('reallysecurecode')
expect(res.code).toBe('invalid_auth_code')
expect(res.message).toBe('bad auth code')

expect(simpleOTP.getUser()).toBeNull()
expect(simpleOTP.isAuthenticated()).toBe(false)
})

test('auth() returns a networking error in the response when the http call response does not have a response prop', async() => {
const simpleOTP = new SimpleOTP('mocksiteid')
const mockPost = vi.spyOn(http, 'post')
mockPost.mockImplementation(() => {
throw {
response2: {
data: {
code: 'invalid_auth_code',
message: 'bad auth code',
data: { id: 'someid', email: '[email protected]', token: 'reallysecuretoken' }
}
}
}
})

const res = await simpleOTP.auth('reallysecurecode')
expect(res.code).toBe(AuthStatusCode.NetworkingError.description)
expect(res.message).toBe('Could not connect to the server. Try again in a few moments.')

expect(simpleOTP.getUser()).toBeNull()
expect(simpleOTP.isAuthenticated()).toBe(false)
})


test('authWithURLCode() updates localStorage with the user details when the http call succeeds', async() => {
const simpleOTP = new SimpleOTP('mocksiteid')
Expand All @@ -85,7 +163,7 @@ test('authWithURLCode() updates localStorage with the user details when the http

window.location = { search: '?simpleotp_code=reallysecurecode'}

const authResponse = await simpleOTP.authWithURLCode('[email protected]')
const authResponse = await simpleOTP.authWithURLCode()
expect(authResponse).toBeTruthy()
expect(authResponse.data).toBeTruthy()
expect(authResponse.data.email).toBe('[email protected]')
Expand All @@ -107,7 +185,7 @@ test('authWithURLCode() throws when the code is missing from the url params', as

window.location = { search: '?not_a_simpleotp_code=reallysecurecode'}

expect(async () => await simpleOTP.authWithURLCode('[email protected]')).rejects.toThrow()
expect(async () => await simpleOTP.authWithURLCode()).rejects.toThrow()
expect(simpleOTP.getUser()).toBeNull()
})

Expand All @@ -128,7 +206,7 @@ test('authWithURLCode() returns the error code in the response when the http cal

window.location = { search: '?simpleotp_code=reallysecurecode'}

const res = await simpleOTP.authWithURLCode('[email protected]')
const res = await simpleOTP.authWithURLCode()
expect(res.code).toBe('invalid_auth_code')
expect(res.message).toBe('bad auth code')

Expand All @@ -153,7 +231,7 @@ test('authWithURLCode() returns a networking error in the response when the http

window.location = { search: '?simpleotp_code=reallysecurecode'}

const res = await simpleOTP.authWithURLCode('[email protected]')
const res = await simpleOTP.authWithURLCode()
expect(res.code).toBe(AuthStatusCode.NetworkingError.description)
expect(res.message).toBe('Could not connect to the server. Try again in a few moments.')

Expand All @@ -170,7 +248,7 @@ test('isAuthenticated() returns false after the user is signed out', async() =>

window.location = { search: '?simpleotp_code=reallysecurecode'}

const user = await simpleOTP.authWithURLCode('[email protected]')
const user = await simpleOTP.authWithURLCode()
expect(user).toBeTruthy()
expect(simpleOTP.getUser()).toBeTruthy()
expect(simpleOTP.isAuthenticated()).toBe(true)
Expand Down
46 changes: 30 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,25 +115,39 @@ export class SimpleOTP {
throw Error(SIMPLEOTP_CODE_PARAM_NAME + ' was not found in the url params.')
}

let response = null
try {
response = await http.post(`${this.apiURL}/v1/sites/${this.siteID}/auth`, { code })
} catch(e) {
const errorHTTPResponseData = e.response?.data
if (errorHTTPResponseData) {
return new SiteAuthResponse(errorHTTPResponseData.code, errorHTTPResponseData.message, errorHTTPResponseData.data)
} else {
return new SiteAuthResponse(SignInStatusCode.NetworkingError.description, NETWORKING_ERROR_MESSAGE, null)
return await this.auth(code)
}

/**
* Authenticates a user based on the code param passed to this method.
* The User is also saved in localStorage so that you can reference it elsewhere in the app. Use getUser() for this purpose.
* @param {string} code
* @returns {Promise<SiteAuthResponse>}
*/
async auth(code) {
if (!code) {
throw Error('code must be specified to use the auth method')
}

let response = null
try {
response = await http.post(`${this.apiURL}/v1/sites/${this.siteID}/auth`, { code })
} catch(e) {
const errorHTTPResponseData = e.response?.data
if (errorHTTPResponseData) {
return new SiteAuthResponse(errorHTTPResponseData.code, errorHTTPResponseData.message, errorHTTPResponseData.data)
} else {
return new SiteAuthResponse(SignInStatusCode.NetworkingError.description, NETWORKING_ERROR_MESSAGE, null)
}
}

const httpResponseData = response.data
const apiResponseData = httpResponseData.data
const user = new AuthenticatedUser(apiResponseData.id, apiResponseData.email, apiResponseData.token)
localStorage.setItem(this.simpleOTPUserKey, JSON.stringify(user))
return new SiteAuthResponse(httpResponseData.code, httpResponseData.message, httpResponseData.data)
}

const httpResponseData = response.data
const apiResponseData = httpResponseData.data
const user = new AuthenticatedUser(apiResponseData.id, apiResponseData.email, apiResponseData.token)
localStorage.setItem(this.simpleOTPUserKey, JSON.stringify(user))
return new SiteAuthResponse(httpResponseData.code, httpResponseData.message, httpResponseData.data)
}

/**
* Fetches the currently authenticated user, if any, from localStorage and returns it. If there isn't an authenticated user,
* this function returns null.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@simpleotp/core",
"version": "1.0.1",
"version": "1.1.0",
"description": "",
"main": "index.js",
"scripts": {
Expand Down

0 comments on commit 5740bb9

Please sign in to comment.