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

Add ability to sign out by email #247

Merged
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
5 changes: 4 additions & 1 deletion test/1-api-sync-mode-sqlite.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ const agent = request.agent(app)

const {
apiSyncModeSqliteTestCases,
signUpTestCase
signUpTestCase,
removeUserTestCases
} = require('./test-cases')

let wrkReportServiceApi = null
Expand Down Expand Up @@ -94,4 +95,6 @@ describe('Sync mode API with SQLite', () => {

signUpTestCase(agent, params)
apiSyncModeSqliteTestCases(agent, params)
signUpTestCase(agent, params)
removeUserTestCases(agent, params)
})
8 changes: 7 additions & 1 deletion test/4-sub-account.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ const agent = request.agent(app)
const {
apiSyncModeSqliteTestCases,
additionalApiSyncModeSqliteTestCases,
signUpTestCase
signUpTestCase,
removeUserTestCases
} = require('./test-cases')

let wrkReportServiceApi = null
Expand Down Expand Up @@ -429,5 +430,10 @@ describe('Sub-account', () => {

additionalApiSyncModeSqliteTestCases(agent, params)
})
describe('Removing sub-account API', () => {
before(beforeFn)

removeUserTestCases(agent, params)
})
})
})
2 changes: 1 addition & 1 deletion test/test-cases/api-sync-mode-sqlite-test-cases.js
Original file line number Diff line number Diff line change
Expand Up @@ -3354,7 +3354,7 @@ module.exports = (
assert.isNotOk(res.body.result)
})

it('it should be successfully performed by the removeUser method', async function () {
it('it should be successfully performed by the removeUser method with token', async function () {
this.timeout(5000)

const res = await agent
Expand Down
4 changes: 3 additions & 1 deletion test/test-cases/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ const additionalApiSyncModeSqliteTestCases = require(
)
const signUpTestCase = require('./sign-up-test-case')
const getSyncProgressTestCase = require('./get-sync-progress-test-case')
const removeUserTestCases = require('./remove-user-test-cases')

module.exports = {
apiSyncModeSqliteTestCases,
additionalApiSyncModeSqliteTestCases,
signUpTestCase,
getSyncProgressTestCase
getSyncProgressTestCase,
removeUserTestCases
}
91 changes: 91 additions & 0 deletions test/test-cases/remove-user-test-cases.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use strict'

const { assert } = require('chai')

module.exports = (
agent,
params = {}
) => {
const {
basePath,
auth: {
email,
password,
isSubAccount
}
} = params
const auth = { token: '' }

it('it should be successfully performed by the signIn method', async function () {
this.timeout(5000)

const res = await agent
.post(`${basePath}/json-rpc`)
.type('json')
.send({
auth: {
email,
password,
isSubAccount
},
method: 'signIn',
id: 5
})
.expect('Content-Type', /json/)
.expect(200)

assert.isObject(res.body)
assert.propertyVal(res.body, 'id', 5)
assert.isObject(res.body.result)
assert.strictEqual(res.body.result.email, email)
assert.strictEqual(res.body.result.isSubAccount, isSubAccount)
assert.isString(res.body.result.token)

auth.token = res.body.result.token
})

it('it should be successfully performed by the removeUser method with email', async function () {
this.timeout(5000)

const res = await agent
.post(`${basePath}/json-rpc`)
.type('json')
.send({
auth: {
email,
password,
isSubAccount
},
method: 'removeUser',
id: 5
})
.expect('Content-Type', /json/)
.expect(200)

assert.isObject(res.body)
assert.propertyVal(res.body, 'id', 5)
assert.isBoolean(res.body.result)
assert.isOk(res.body.result)
})

it('it should not be successfully performed by the verifyUser method', async function () {
this.timeout(5000)

const res = await agent
.post(`${basePath}/json-rpc`)
.type('json')
.send({
auth,
method: 'verifyUser',
id: 5
})
.expect('Content-Type', /json/)
.expect(401)

assert.isObject(res.body)
assert.isObject(res.body.error)
assert.propertyVal(res.body.error, 'code', 401)
assert.propertyVal(res.body.error, 'message', 'Unauthorized')
assert.propertyVal(res.body, 'id', 5)
})
}
64 changes: 45 additions & 19 deletions workers/loc.api/sync/authenticator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class Authenticator {
* It may only work for one grenache worker instance
*/
this.userSessions = new Map()
this.userTokenMapByEmail = new Map()
}

async signUp (args, opts) {
Expand Down Expand Up @@ -302,7 +303,7 @@ class Authenticator {
typeof token === 'string'
)
? token
: this.getUserSessionByEmail({ email, isSubAccount }).token
: this.getUserSessionByEmail({ email, isSubAccount })?.token
const createdToken = (
existedToken &&
typeof existedToken === 'string'
Expand Down Expand Up @@ -361,7 +362,11 @@ class Authenticator {
throw new AuthError()
}

this.removeUserSessionByToken(token)
this.removeUserSession({
email,
isSubAccount,
token
})

if (isSchedulerEnabled) {
await this.dao.updateRecordOf(
Expand Down Expand Up @@ -510,7 +515,7 @@ class Authenticator {

const existedToken = this.getUserSessionByEmail(
{ email, isSubAccount }
).token
)?.token
const createdToken = (
existedToken &&
typeof existedToken === 'string'
Expand Down Expand Up @@ -602,7 +607,7 @@ class Authenticator {
token,
isReturnedPassword
)
const { apiKey, apiSecret } = { ...session }
const { apiKey, apiSecret } = session ?? {}

if (
!apiKey ||
Expand Down Expand Up @@ -830,15 +835,21 @@ class Authenticator {
throw new UserRemovingError()
}

this.removeUserSessionByToken(token)
this.removeUserSession({
email,
isSubAccount,
token
})

return true
}

setUserSession (user) {
const { token } = user ?? {}
const tokenKey = this._getTokenKeyByEmailField(user)

this.userSessions.set(token, { ...user })
this.userTokenMapByEmail.set(tokenKey, token)
}

getUserSessionByToken (token, isReturnedPassword) {
Expand All @@ -847,18 +858,10 @@ class Authenticator {
return pickSessionProps(session, isReturnedPassword)
}

getUserSessionByEmail (args, isReturnedPassword) {
const {
email,
isSubAccount = false
} = args ?? {}
const keyVal = [...this.userSessions].find(([, session]) => {
return (
email === session.email &&
isSubAccount === session.isSubAccount
)
})
const session = Array.isArray(keyVal) ? keyVal[1] : {}
getUserSessionByEmail (user, isReturnedPassword) {
const tokenKey = this._getTokenKeyByEmailField(user)
const token = this.userTokenMapByEmail.get(tokenKey)
const session = this.userSessions.get(token)

return pickSessionProps(session, isReturnedPassword)
}
Expand All @@ -873,8 +876,19 @@ class Authenticator {
return new Map(sessionsMap)
}

removeUserSessionByToken (token) {
return this.userSessions.delete(token)
removeUserSession (user) {
const { token } = user ?? {}
const tokenKey = this._getTokenKeyByEmailField(user)
const _token = (
token &&
typeof token === 'string'
)
? token
: this.userTokenMapByEmail.get(tokenKey)

this.userTokenMapByEmail.delete(tokenKey)

return this.userSessions.delete(_token)
}

async decryptApiKeys (password, users) {
Expand Down Expand Up @@ -905,6 +919,18 @@ class Authenticator {

return isArray ? res : res[0]
}

_getTokenKeyByEmailField (user) {
const {
email,
isSubAccount
} = user ?? {}
const suffix = isSubAccount
? ':sub-account'
: ''

return `${email}${suffix}`
}
}

decorateInjectable(Authenticator, depsTypes)
Expand Down