Skip to content

Commit

Permalink
Rework verify-email ux
Browse files Browse the repository at this point in the history
  • Loading branch information
knolleary committed Jul 29, 2022
1 parent f972128 commit 6946d6b
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 3 deletions.
10 changes: 7 additions & 3 deletions frontend/src/App.vue
Expand Up @@ -15,7 +15,7 @@
</main>
</template>
<!-- Platform Entry Point -->
<template v-else-if="user && !user.password_expired">
<template v-else-if="user && !user.password_expired && user.email_verified">
<ff-layout-platform>
<router-view></router-view>
</ff-layout-platform>
Expand All @@ -24,6 +24,10 @@
<template v-else-if="user && user.password_expired">
<PasswordExpired/>
</template>
<!-- Email Verification Required -->
<template v-else-if="user && !user.email_verified">
<UnverifiedEmail/>
</template>
<template v-else-if="!loginRequired">
<router-view></router-view>
</template>
Expand All @@ -40,7 +44,7 @@ import Login from '@/pages/Login.vue'
import Loading from '@/components/Loading'
import Offline from '@/components/Offline'
import PasswordExpired from '@/pages/PasswordExpired.vue'
import UnverifiedEmail from '@/pages/UnverifiedEmail.vue'
import FFLayoutPlatform from '@/layouts/Platform.vue'
export default {
Expand All @@ -54,13 +58,13 @@ export default {
components: {
Login,
PasswordExpired,
UnverifiedEmail,
Loading,
Offline,
'ff-layout-platform': FFLayoutPlatform
},
mounted () {
this.$store.dispatch('account/checkState')
this.$store.dispatch('account/countNotifications')
}
}
</script>
Expand Down
45 changes: 45 additions & 0 deletions frontend/src/pages/UnverifiedEmail.vue
@@ -0,0 +1,45 @@
<template>
<ff-layout-box>
<form class="px-4 sm:px-6 lg:px-8 mt-8 space-y-6 max-w-md" @submit.prevent>
<p>
Before you can access the platform, we need to verify your email
address.
</p>
<p>
We sent you an email with a link to click when you signed up.
</p>
<ff-button :disabled="sent" @click="resend">
<span v-if="!sent">Resend email</span>
<span v-else>Sent</span>
</ff-button>
</form>
</ff-layout-box>
</template>

<script>
import { mapState } from 'vuex'
import userApi from '@/api/user'
import FFLayoutBox from '@/layouts/Box'
export default {
name: 'UnverifiedEmail',
methods: {
async resend () {
if (!this.sent) {
this.sent = true
await userApi.triggerVerification()
}
}
},
computed: mapState('account', ['user']),
data () {
return {
sent: false
}
},
components: {
'ff-layout-box': FFLayoutBox
}
}
</script>
92 changes: 92 additions & 0 deletions test/unit/forge/routes/api/user_spec.js
@@ -0,0 +1,92 @@
const should = require('should') // eslint-disable-line
const setup = require('../setup')
const FF_UTIL = require('flowforge-test-utils')
const { Roles } = FF_UTIL.require('forge/lib/roles')

describe('User API', async function () {
let app
const TestObjects = {}

beforeEach(async function () {
app = await setup({ features: { devices: true } })

// alice : admin, team owner
// bob
// chris : (unverified_email)

// ATeam ( alice (owner), bob (owner), chris)
// BTeam ( bob (owner), chris)

// Alice create in setup()
TestObjects.alice = await app.db.models.User.byUsername('alice')
TestObjects.bob = await app.db.models.User.create({ username: 'bob', name: 'Bob Solo', email: 'bob@example.com', email_verified: true, password: 'bbPassword', admin: true })
TestObjects.chris = await app.db.models.User.create({ username: 'chris', name: 'Chris Kenobi', email: 'chris@example.com', password: 'ccPassword' })

// ATeam create in setup()
TestObjects.ATeam = await app.db.models.Team.byName('ATeam')
TestObjects.BTeam = await app.db.models.Team.create({ name: 'BTeam' })

// Alice set as ATeam owner in setup()
await TestObjects.ATeam.addUser(TestObjects.bob, { through: { role: Roles.Owner } })
await TestObjects.ATeam.addUser(TestObjects.chris, { through: { role: Roles.Member } })
await TestObjects.BTeam.addUser(TestObjects.bob, { through: { role: Roles.Owner } })
await TestObjects.BTeam.addUser(TestObjects.chris, { through: { role: Roles.Member } })

TestObjects.tokens = {}
})

async function login (username, password) {
const response = await app.inject({
method: 'POST',
url: '/account/login',
payload: { username, password, remember: false }
})
response.cookies.should.have.length(1)
response.cookies[0].should.have.property('name', 'sid')
TestObjects.tokens[username] = response.cookies[0].value
}

afterEach(async function () {
await app.close()
})

describe('User settings', async function () {
it('returns 401 on /user if not logged in', async function () {
// await login('alice', 'aaPassword')
// await login('bob', 'bbPassword')
// await login('chris', 'ccPassword')
const response = await app.inject({
method: 'GET',
url: '/api/v1/user'
})
response.statusCode.should.equal(401)
})
it('return user info for logged in user', async function () {
await login('alice', 'aaPassword')
const response = await app.inject({
method: 'GET',
url: '/api/v1/user',
cookies: { sid: TestObjects.tokens.alice }
})
response.statusCode.should.equal(200)
const result = response.json()
result.should.have.property('id', TestObjects.alice.hashid)
result.should.have.property('username', TestObjects.alice.username)
result.should.have.property('email', TestObjects.alice.email)
})
it('return user info for unverified_email user', async function () {
await login('chris', 'ccPassword')
const response = await app.inject({
method: 'GET',
url: '/api/v1/user',
cookies: { sid: TestObjects.tokens.chris }
})
response.statusCode.should.equal(200)
const result = response.json()
result.should.have.property('id', TestObjects.chris.hashid)
result.should.have.property('username', TestObjects.chris.username)
result.should.have.property('email', TestObjects.chris.email)
result.should.have.property('email_verified', false)
})
})
})

0 comments on commit 6946d6b

Please sign in to comment.