diff --git a/api/src/email/sendgrid.js b/api/src/email/sendgrid.js
index 20e1e9ffe..a30969ea8 100644
--- a/api/src/email/sendgrid.js
+++ b/api/src/email/sendgrid.js
@@ -7,7 +7,7 @@ const sendTeamMemberInvite = function(email, team) {
const API_KEY = process.env.SG_APIKEY
const token = jwt.sign({ email: email, team: team.slug }, API_KEY, { expiresIn: '7d' })
let origin = 'https://teams.advancedalgos.net'
- const params = '/activate-team-membership?token='
+ const params = '/activate-team-membership/'
if (dev){
origin = 'http://localhost:3001'
}
@@ -67,4 +67,21 @@ const sendTeamMemberInvite = function(email, team) {
return sendVerify
}
-module.exports = { sendTeamMemberInvite }
+const verifyInviteToken = async function(token) {
+ const API_KEY = process.env.SG_APIKEY
+ let verifiedToken
+ try {
+ verifiedToken = await jwt.verify(token, API_KEY, {maxAge: '7d'})
+ } catch(err) {
+ if (err.name === "TokenExpiredError"){
+ throw new Error ("Error: Token Expired. Please request new invitation or apply to the team again")
+ } else {
+ throw new Error (`Error: Token error - ${err.message}`)
+ }
+ }
+
+ console.log('verifyInviteToken', verifiedToken)
+ return verifiedToken
+}
+
+module.exports = { sendTeamMemberInvite, verifyInviteToken }
diff --git a/api/src/index.js b/api/src/index.js
index 92b24b1e1..13f354638 100644
--- a/api/src/index.js
+++ b/api/src/index.js
@@ -14,7 +14,7 @@ const { getMember } = require('./auth/middleware/getMember')
const { validateIdToken } = require('./auth/validateIdToken')
const { directiveResolvers } = require('./auth/authDirectives')
-const { sendTeamMemberInvite } = require('./email/sendgrid')
+const { sendTeamMemberInvite, verifyInviteToken } = require('./email/sendgrid')
const {
createStoragePipline,
@@ -136,6 +136,24 @@ const resolvers = {
}
return member
},
+ async verifyTeamInvite(parent, { token }, ctx, info) {
+ let verifiedToken = null
+ let team = null
+ try {
+ verifiedToken = await verifyInviteToken(token)
+ team = {
+ email: verifiedToken.email,
+ team: {
+ slug: verifiedToken.team
+ }
+ }
+ console.log('verifyTeamInvite.token: ', await verifiedToken)
+ } catch (err) {
+ console.log('verifyTeamInvite. err: ', err)
+ throw new Error(err.message)
+ }
+ return team
+ },
async createTeam(parent, { name, slug }, ctx, info) {
const authId = ctx.request.user.sub
return ctx.db.mutation.createTeam({ data: { name: name, slug: slug, owner: authId, members: { create: { member:{ connect:{ authId: authId} }, role: 'OWNER' } }, profile:{create:{avatar:"a"} } } }, TEAMS_FRAGMENT)
diff --git a/api/src/schema.graphql b/api/src/schema.graphql
index b6d7c40e7..0b969a608 100644
--- a/api/src/schema.graphql
+++ b/api/src/schema.graphql
@@ -20,6 +20,7 @@ type Query {
type Mutation {
authenticate(idToken: String!): Member
+ verifyTeamInvite(token: String!): TeamMembers
createTeam(name: String!, slug: String!): Team @isAuthenticated
updateTeamProfile(slug: String!, description: String, motto: String, avatar: String, banner: String): Team @isAuthenticated
deleteTeam(slug: String!): Team @isAuthenticated @isOwner(type: "Team")
diff --git a/client/package-lock.json b/client/package-lock.json
index c0fbda736..dbfc71ab6 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -6266,8 +6266,7 @@
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
- "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
- "dev": true
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
},
"decompress-response": {
"version": "3.3.0",
@@ -12102,6 +12101,18 @@
"prepend-http": "^1.0.0",
"query-string": "^4.1.0",
"sort-keys": "^1.0.0"
+ },
+ "dependencies": {
+ "query-string": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
+ "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
+ "dev": true,
+ "requires": {
+ "object-assign": "^4.1.0",
+ "strict-uri-encode": "^1.0.0"
+ }
+ }
}
},
"npm-run-all": {
@@ -13791,13 +13802,19 @@
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
},
"query-string": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
- "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
- "dev": true,
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.1.0.tgz",
+ "integrity": "sha512-pNB/Gr8SA8ff8KpUFM36o/WFAlthgaThka5bV19AD9PNTH20Pwq5Zxodif2YyHwrctp6SkL4GqlOot0qR/wGaw==",
"requires": {
- "object-assign": "^4.1.0",
- "strict-uri-encode": "^1.0.0"
+ "decode-uri-component": "^0.2.0",
+ "strict-uri-encode": "^2.0.0"
+ },
+ "dependencies": {
+ "strict-uri-encode": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
+ "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
+ }
}
},
"querystring": {
diff --git a/client/package.json b/client/package.json
index 34a09ec19..950e4394c 100644
--- a/client/package.json
+++ b/client/package.json
@@ -89,6 +89,7 @@
"graphql": "^0.13.2",
"graphql-tag": "^2.9.2",
"prop-types": "^15.6.2",
+ "query-string": "^6.1.0",
"react": "^16.5.0",
"react-apollo": "^2.1.11",
"react-dom": "^16.5.0",
diff --git a/client/src/App.js b/client/src/App.js
index 8ed26d1a8..b0fe69f45 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -19,6 +19,7 @@ import {
NavBar,
Footer,
Callback,
+ AcceptTeamInvite,
Home,
About,
Dashboard,
@@ -171,6 +172,11 @@ class App extends Component {
return
}}
/>
+ }
+ />
diff --git a/client/src/auth/index.js b/client/src/auth/index.js
index 3c0971845..c62f8c501 100644
--- a/client/src/auth/index.js
+++ b/client/src/auth/index.js
@@ -16,7 +16,19 @@ const AUTHENTICATE = gql`
}
}
`
-const lock = new Auth0Lock(AUTH_CONFIG.clientId, AUTH_CONFIG.domain, {
+
+const VERIFY_TEAM_INVITE = gql`
+ mutation verifyTeamInvite($token: String!) {
+ verifyTeamInvite(token: $token) {
+ email
+ team {
+ slug
+ }
+ }
+ }
+`
+
+export const defaultOptions = {
oidcConformant: true,
autoclose: true,
auth: {
@@ -28,7 +40,27 @@ const lock = new Auth0Lock(AUTH_CONFIG.clientId, AUTH_CONFIG.domain, {
scope: `openid profile email user_metadata app_metadata picture`
}
}
-})
+}
+
+export const inviteOptions = {
+ oidcConformant: true,
+ autoclose: false,
+ allowedConnections: ['Username-Password-Authentication'],
+ allowShowPassword: true,
+ auth: {
+ sso: false,
+ redirectUrl: window.location.origin + '/callback',
+ responseType: 'token id_token',
+ audience: `${AUTH_CONFIG.api_audience}`,
+ params: {
+ state: '',
+ scope: `email`
+ }
+ },
+ prefill: { email: '' }
+}
+
+let lock = new Auth0Lock(AUTH_CONFIG.clientId, AUTH_CONFIG.domain, defaultOptions)
class Auth {
constructor (cb, apolloClient) {
@@ -37,6 +69,7 @@ class Auth {
this.apolloClient = apolloClient
this.cb = cb.bind(this)
this.login = this.login.bind(this)
+ this.loginInvite = this.loginInvite.bind(this)
this.logout = this.logout.bind(this)
this.isAuthenticated = this.isAuthenticated.bind(this)
this.checkSession = this.checkSession.bind(this)
@@ -47,6 +80,29 @@ class Auth {
lock.show()
}
+ async loginInvite (jwt) {
+ let email
+ let team
+ try {
+ const data = await client.mutate({
+ mutation: VERIFY_TEAM_INVITE,
+ variables: { token: jwt }
+ })
+
+ console.log('loginInvite auth: ', await data.data.verifyTeamInvite)
+ setItem('invite', JSON.stringify(data.data.verifyTeamInvite))
+
+ email = data.data.verifyTeamInvite.email
+ team = data.data.verifyTeamInvite.team.slug
+ } catch (err) {
+ return console.log('loginInvite err: ', err)
+ }
+ inviteOptions.prefill.email = email
+ inviteOptions.auth.params.state = `${email}|${team}`
+ let lockInvite = new Auth0Lock(AUTH_CONFIG.clientId, AUTH_CONFIG.domain, inviteOptions)
+ lockInvite.show()
+ }
+
handleAuthentication () {
// Add a callback for Lock's `authenticated` event
lock.on('authenticated', this.setSession.bind(this))
diff --git a/client/src/views/index.js b/client/src/views/index.js
index 195ec74ff..30ab09b90 100644
--- a/client/src/views/index.js
+++ b/client/src/views/index.js
@@ -5,6 +5,7 @@ import Home from './home'
import About from './about'
import Dashboard from './dashboard'
import Teams from './teams'
+import AcceptTeamInvite from './teams/AcceptTeamInvite'
import { theme, globalStyles } from './theme'
@@ -12,6 +13,7 @@ export {
NavBar,
Footer,
Callback,
+ AcceptTeamInvite,
Home,
About,
Dashboard,
diff --git a/client/src/views/teams/AcceptTeamInvite.js b/client/src/views/teams/AcceptTeamInvite.js
new file mode 100644
index 000000000..23f0d870c
--- /dev/null
+++ b/client/src/views/teams/AcceptTeamInvite.js
@@ -0,0 +1,31 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+
+import queryString from 'query-string'
+
+import { MessageCard } from '../common/'
+
+class AcceptTeamInvite extends Component {
+ render () {
+ const { location, auth } = this.props
+ const param = queryString.parse(location.search)
+ console.log(param, auth)
+
+ if (auth === undefined) {
+ return
+ }
+
+ auth.loginInvite(param.token)
+
+ return (
+
+ )
+ }
+}
+
+AcceptTeamInvite.propTypes = {
+ location: PropTypes.object.isRequired,
+ auth: PropTypes.object
+}
+
+export default AcceptTeamInvite