diff --git a/backend/.env.template b/backend/.env.template
index fc9766478..2d2374698 100644
--- a/backend/.env.template
+++ b/backend/.env.template
@@ -17,6 +17,7 @@ PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78"
SENTRY_DSN_BACKEND=
COMMIT=
PUBLIC_REGISTRATION=false
+INVITE_REGISTRATION=true
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
diff --git a/backend/src/config/index.js b/backend/src/config/index.js
index 47771029b..b7d500b8b 100644
--- a/backend/src/config/index.js
+++ b/backend/src/config/index.js
@@ -82,7 +82,8 @@ const options = {
SUPPORT_URL: links.SUPPORT,
APPLICATION_NAME: metadata.APPLICATION_NAME,
ORGANIZATION_URL: links.ORGANIZATION,
- PUBLIC_REGISTRATION: env.PUBLIC_REGISTRATION === 'true',
+ PUBLIC_REGISTRATION: env.PUBLIC_REGISTRATION === 'true' || false,
+ INVITE_REGISTRATION: env.INVITE_REGISTRATION !== 'false', // default = true
}
// Check if all required configs are present
diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js
index 7aeb7252a..e1b2feebe 100644
--- a/backend/src/middleware/permissionsMiddleware.js
+++ b/backend/src/middleware/permissionsMiddleware.js
@@ -87,7 +87,7 @@ const noEmailFilter = rule({
return !('email' in args)
})
-const publicRegistration = rule()(() => !!CONFIG.PUBLIC_REGISTRATION)
+const publicRegistration = rule()(() => CONFIG.PUBLIC_REGISTRATION)
// Permissions
export default shield(
@@ -123,6 +123,7 @@ export default shield(
isValidInviteCode: allow,
queryLocations: isAuthenticated,
availableRoles: isAdmin,
+ getInviteCode: isAuthenticated, // and inviteRegistration
},
Mutation: {
'*': deny,
diff --git a/backend/src/schema/resolvers/inviteCodes.js b/backend/src/schema/resolvers/inviteCodes.js
index 91148a08d..2a0269b54 100644
--- a/backend/src/schema/resolvers/inviteCodes.js
+++ b/backend/src/schema/resolvers/inviteCodes.js
@@ -12,6 +12,52 @@ const uniqueInviteCode = async (session, code) => {
export default {
Query: {
+ getInviteCode: async (_parent, args, context, _resolveInfo) => {
+ const {
+ user: { id: userId },
+ } = context
+ const session = context.driver.session()
+ const readTxResultPromise = session.readTransaction(async (txc) => {
+ const result = await txc.run(
+ `MATCH (user:User {id: $userId})-[:GENERATED]->(ic:InviteCode)
+ WHERE ic.expiresAt IS NULL
+ OR datetime(ic.expiresAt) >= datetime()
+ RETURN properties(ic) AS inviteCodes`,
+ {
+ userId,
+ },
+ )
+ return result.records.map((record) => record.get('inviteCodes'))
+ })
+ try {
+ const inviteCode = await readTxResultPromise
+ if (inviteCode && inviteCode.length > 0) return inviteCode[0]
+ let code = generateInviteCode()
+ while (!(await uniqueInviteCode(session, code))) {
+ code = generateInviteCode()
+ }
+ const writeTxResultPromise = session.writeTransaction(async (txc) => {
+ const result = await txc.run(
+ `MATCH (user:User {id: $userId})
+ MERGE (user)-[:GENERATED]->(ic:InviteCode { code: $code })
+ ON CREATE SET
+ ic.createdAt = toString(datetime()),
+ ic.expiresAt = $expiresAt
+ RETURN ic AS inviteCode`,
+ {
+ userId,
+ code,
+ expiresAt: null,
+ },
+ )
+ return result.records.map((record) => record.get('inviteCode').properties)
+ })
+ const txResult = await writeTxResultPromise
+ return txResult[0]
+ } finally {
+ session.close()
+ }
+ },
MyInviteCodes: async (_parent, args, context, _resolveInfo) => {
const {
user: { id: userId },
diff --git a/backend/src/schema/types/type/InviteCode.gql b/backend/src/schema/types/type/InviteCode.gql
index 8ad7851a2..3293c735b 100644
--- a/backend/src/schema/types/type/InviteCode.gql
+++ b/backend/src/schema/types/type/InviteCode.gql
@@ -14,4 +14,5 @@ type Mutation {
type Query {
MyInviteCodes: [InviteCode]
isValidInviteCode(code: ID!): Boolean
+ getInviteCode: InviteCode
}
diff --git a/webapp/.env.template b/webapp/.env.template
index 1acad49b4..7373255a9 100644
--- a/webapp/.env.template
+++ b/webapp/.env.template
@@ -1,5 +1,6 @@
SENTRY_DSN_WEBAPP=
COMMIT=
PUBLIC_REGISTRATION=false
+INVITE_REGISTRATION=true
WEBSOCKETS_URI=ws://localhost:3000/api/graphql
GRAPHQL_URI=http://localhost:4000/
diff --git a/webapp/assets/_new/icons/svgs/copy.svg b/webapp/assets/_new/icons/svgs/copy.svg
new file mode 100644
index 000000000..1792f2002
--- /dev/null
+++ b/webapp/assets/_new/icons/svgs/copy.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/webapp/components/InviteButton/InviteButton.vue b/webapp/components/InviteButton/InviteButton.vue
new file mode 100644
index 000000000..9eec37bc2
--- /dev/null
+++ b/webapp/components/InviteButton/InviteButton.vue
@@ -0,0 +1,113 @@
+
+