Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.
Open
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
11 changes: 11 additions & 0 deletions config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ module.exports = {
*/
dateFormat: 'YYYY-MM-DD HH:mm:ss',

/*
|--------------------------------------------------------------------------
| Valid For
|--------------------------------------------------------------------------
|
| The default time in hours the token is valid for. This can be changed for
| an individual token by calling `isValidFor()` before generating it.
|
*/
validFor: 24,

/*
|--------------------------------------------------------------------------
| Validation messages
Expand Down
47 changes: 44 additions & 3 deletions src/Persona.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ class Persona {
model: 'App/Models/User',
newAccountState: 'pending',
verifiedAccountState: 'active',
dateFormat: 'YYYY-MM-DD HH:mm:ss'
dateFormat: 'YYYY-MM-DD HH:mm:ss',
validFor: 24
})

/**
Expand All @@ -59,6 +60,7 @@ class Persona {

this._encrypter = Encryption.getInstance({ hmac: false })
this._model = null
this._validFor = null
}

/**
Expand Down Expand Up @@ -157,7 +159,7 @@ class Persona {
query
.where('type', type)
.where('is_revoked', false)
.where('updated_at', '>=', moment().subtract(24, 'hours').format(this.config.dateFormat))
.where('expires_at', '>=', moment().format(this.config.dateFormat))
}

/**
Expand Down Expand Up @@ -187,7 +189,16 @@ class Persona {
}

const token = this._encrypter.encrypt(randtoken.generate(16))
await user.tokens().create({ type, token })
const expiresAt = moment().add(this.getValidFor(), 'hours').format(this.config.dateFormat)

this._validFor = null

await user.tokens().create({
type,
token,
expires_at: expiresAt
})

return token
}

Expand Down Expand Up @@ -276,6 +287,36 @@ class Persona {
return this.getModel().table
}

/**
* Returns the duration in hours the a generated token should be valid for
*
* @method getValidFor
*
* @return {Number}
*/
getValidFor () {
if (this._validFor) {
return this._validFor
}

return this.config.validFor
}

/**
* Sets the duration in hours the a generated token should be valid for
*
* @method isValidFor
*
* @param {Number} payload
*
* @return {Persona}
*/
isValidFor (payload) {
this._validFor = payload

return this
}

/**
* Returns an object of registeration rules
*
Expand Down
75 changes: 68 additions & 7 deletions test/persona.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ test.group('Persona', (group) => {
user_id: user.id,
is_revoked: false,
created_at: moment().subtract(2, 'days').format('YYYY-MM-DD HH:mm:ss'),
updated_at: moment().subtract(2, 'days').format('YYYY-MM-DD HH:mm:ss')
updated_at: moment().subtract(2, 'days').format('YYYY-MM-DD HH:mm:ss'),
expires_at: moment().subtract(1, 'days').format('YYYY-MM-DD HH:mm:ss')
})

assert.plan(2)
Expand All @@ -228,7 +229,8 @@ test.group('Persona', (group) => {
user_id: user.id,
is_revoked: false,
created_at: moment().format('YYYY-MM-DD HH:mm:ss'),
updated_at: moment().format('YYYY-MM-DD HH:mm:ss')
updated_at: moment().format('YYYY-MM-DD HH:mm:ss'),
expires_at: moment().add(2, 'days').format('YYYY-MM-DD HH:mm:ss')
})

assert.plan(2)
Expand All @@ -250,7 +252,8 @@ test.group('Persona', (group) => {
is_revoked: false,
user_id: user.id,
created_at: moment().format('YYYY-MM-DD HH:mm:ss'),
updated_at: moment().format('YYYY-MM-DD HH:mm:ss')
updated_at: moment().format('YYYY-MM-DD HH:mm:ss'),
expires_at: moment().add(2, 'days').format('YYYY-MM-DD HH:mm:ss')
})

await this.persona.verifyEmail('hello')
Expand All @@ -271,7 +274,8 @@ test.group('Persona', (group) => {
is_revoked: false,
user_id: user.id,
created_at: moment().format('YYYY-MM-DD HH:mm:ss'),
updated_at: moment().format('YYYY-MM-DD HH:mm:ss')
updated_at: moment().format('YYYY-MM-DD HH:mm:ss'),
expires_at: moment().add(2, 'days').format('YYYY-MM-DD HH:mm:ss')
})

await this.persona.verifyEmail('hello')
Expand Down Expand Up @@ -604,7 +608,8 @@ test.group('Persona', (group) => {
user_id: user.id,
is_revoked: false,
created_at: moment().format('YYYY-MM-DD HH:mm:ss'),
updated_at: moment().format('YYYY-MM-DD HH:mm:ss')
updated_at: moment().format('YYYY-MM-DD HH:mm:ss'),
expires_at: moment().add(2, 'days').format('YYYY-MM-DD HH:mm:ss')
})

try {
Expand All @@ -629,7 +634,8 @@ test.group('Persona', (group) => {
user_id: user.id,
is_revoked: false,
created_at: moment().subtract(2, 'days').format('YYYY-MM-DD HH:mm:ss'),
updated_at: moment().subtract(2, 'days').format('YYYY-MM-DD HH:mm:ss')
updated_at: moment().subtract(2, 'days').format('YYYY-MM-DD HH:mm:ss'),
expires_at: moment().subtract(1, 'days').format('YYYY-MM-DD HH:mm:ss')
})

try {
Expand All @@ -655,7 +661,8 @@ test.group('Persona', (group) => {
user_id: user.id,
is_revoked: false,
created_at: moment().format('YYYY-MM-DD HH:mm:ss'),
updated_at: moment().format('YYYY-MM-DD HH:mm:ss')
updated_at: moment().format('YYYY-MM-DD HH:mm:ss'),
expires_at: moment().add(2, 'days').format('YYYY-MM-DD HH:mm:ss')
})

await this.persona.updatePasswordByToken('hello', { password: 'newsecret', password_confirmation: 'newsecret' })
Expand Down Expand Up @@ -749,4 +756,58 @@ test.group('Persona', (group) => {
assert.equal(users.size(), 1)
assert.equal(users.first().email, 'foo@bar.com')
})

test('generated tokens are valid for 24 hours by default', async (assert) => {
const user = await getUser().create({
username: 'virk',
email: 'foo@bar.com',
account_status: 'active',
password: 'secret'
})

const token = await this.persona.generateToken(user, 'email')

const tokenEntity = await this.persona.getToken(token, 'email')
assert.equal(tokenEntity.expires_at, moment().add(1, 'days').format('YYYY-MM-DD HH:mm:ss'))
})

test('tokens without an expiry date are invalid', async (assert) => {
const user = await getUser().create({ email: 'foo@bar.com' })

await use('Database').table('tokens').insert({
token: 'hello',
type: 'email',
user_id: user.id,
is_revoked: false,
created_at: moment().subtract(2, 'days').format('YYYY-MM-DD HH:mm:ss'),
updated_at: moment().subtract(2, 'days').format('YYYY-MM-DD HH:mm:ss'),
expires_at: null
})

assert.plan(2)

try {
await this.persona.verifyEmail('hello')
} catch ({ message, name }) {
assert.equal(message, 'The token is invalid or expired')
assert.equal(name, 'InvalidTokenException')
}
})

test('the expiry date for a tokens can be changed for each token', async (assert) => {
const user = await getUser().create({
username: 'virk',
email: 'foo@bar.com',
account_status: 'active',
password: 'secret'
})

const token = await this.persona.isValidFor(48).generateToken(user, 'email')
const tokenEntity = await this.persona.getToken(token, 'email')
assert.equal(tokenEntity.expires_at, moment().add(2, 'days').format('YYYY-MM-DD HH:mm:ss'))

const token1 = await this.persona.generateToken(user, 'password')
const tokenEntity1 = await this.persona.getToken(token1, 'password')
assert.equal(tokenEntity1.expires_at, moment().add(1, 'days').format('YYYY-MM-DD HH:mm:ss'))
})
})
1 change: 1 addition & 0 deletions test/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ module.exports = {
table.string('token').notNull()
table.string('type').notNull()
table.boolean('is_revoked').defaultsTo(false)
table.timestamp('expires_at').nullable()
table.timestamps()
})
},
Expand Down