mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-21 07:46:24 +00:00
UBER-221 Confirm registration (#3254)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
89ecba6b9a
commit
0d70ba8363
@ -17,6 +17,7 @@
|
||||
import {
|
||||
ACCOUNT_DB,
|
||||
assignWorkspace,
|
||||
confirmEmail,
|
||||
createAccount,
|
||||
createWorkspace,
|
||||
dropAccount,
|
||||
@ -371,6 +372,16 @@ export function devTool (
|
||||
)
|
||||
})
|
||||
|
||||
program
|
||||
.command('confirm-email <email>')
|
||||
.description('confirm user email')
|
||||
.action(async (email: string, cmd) => {
|
||||
const { mongodbUri } = prepareTools()
|
||||
return await withDatabase(mongodbUri, async (db) => {
|
||||
await confirmEmail(db, email)
|
||||
})
|
||||
})
|
||||
|
||||
program
|
||||
.command('diff-workspace <workspace>')
|
||||
.description('restore workspace transactions and minio resources from previous dump.')
|
||||
|
@ -16,7 +16,7 @@
|
||||
import { popupstore as modal } from '../popups'
|
||||
import PopupInstance from './PopupInstance.svelte'
|
||||
|
||||
export let contentPanel: HTMLElement
|
||||
export let contentPanel: HTMLElement | undefined = undefined
|
||||
|
||||
const instances: PopupInstance[] = []
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
||||
export let zIndex: number
|
||||
export let top: boolean
|
||||
export let close: () => void
|
||||
export let contentPanel: HTMLElement
|
||||
export let contentPanel: HTMLElement | undefined
|
||||
|
||||
let modalHTML: HTMLElement
|
||||
let componentInstance: any
|
||||
@ -68,7 +68,11 @@
|
||||
_close(undefined)
|
||||
}
|
||||
|
||||
const fitPopup = (modalHTML: HTMLElement, element: PopupAlignment | undefined, contentPanel: HTMLElement): void => {
|
||||
const fitPopup = (
|
||||
modalHTML: HTMLElement,
|
||||
element: PopupAlignment | undefined,
|
||||
contentPanel: HTMLElement | undefined
|
||||
): void => {
|
||||
if ((fullSize || docSize) && (element === 'float' || element === 'centered')) {
|
||||
options = fitPopupElement(modalHTML, 'full', contentPanel)
|
||||
options.props.maxHeight = '100vh'
|
||||
|
@ -39,6 +39,8 @@
|
||||
"InviteLimit": "Invite limit:",
|
||||
"GetLink": "Get invite link",
|
||||
"NoLimit": "No limit",
|
||||
"AlreadyJoined": "Already joined?"
|
||||
"AlreadyJoined": "Already joined?",
|
||||
"ConfirmationSent": "A message has been sent to your email containing a link to confirm the your address.",
|
||||
"ConfirmationSent2": "Please follow the link to complete your sign up."
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,8 @@
|
||||
"InviteLimit": "Предел использований:",
|
||||
"GetLink": "Получить ссылку",
|
||||
"NoLimit": "Без предела использований",
|
||||
"AlreadyJoined": "Уже подключены?"
|
||||
"AlreadyJoined": "Уже подключены?",
|
||||
"ConfirmationSent": "На Вашу почту отправлено сообщение, c ссылкой для подтверждения email.",
|
||||
"ConfirmationSent2": "Пожалуйста, перейдите по ссылке для завершения регистрации."
|
||||
}
|
||||
}
|
||||
|
50
plugins/login-resources/src/components/Confirmation.svelte
Normal file
50
plugins/login-resources/src/components/Confirmation.svelte
Normal file
@ -0,0 +1,50 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { OK, setMetadata, Severity, Status } from '@hcengineering/platform'
|
||||
|
||||
import { getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
|
||||
import login from '../plugin'
|
||||
import { confirm } from '../utils'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
let status: Status<any> = OK
|
||||
|
||||
async function check () {
|
||||
const location = getCurrentLocation()
|
||||
if (location.query?.id === undefined || location.query?.id === null) return
|
||||
status = new Status(Severity.INFO, login.status.ConnectingToServer, {})
|
||||
|
||||
const [loginStatus, result] = await confirm(location.query?.id)
|
||||
|
||||
status = loginStatus
|
||||
|
||||
if (result !== undefined) {
|
||||
setMetadata(presentation.metadata.Token, result.token)
|
||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
|
||||
setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
|
||||
const loc = getCurrentLocation()
|
||||
loc.query = undefined
|
||||
loc.path[1] = 'selectWorkspace'
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
check()
|
||||
})
|
||||
</script>
|
@ -0,0 +1,27 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import login from '../plugin'
|
||||
</script>
|
||||
|
||||
<div class="flex-center h-full p-10">
|
||||
<div class="flex-col-center text-center">
|
||||
<h4>
|
||||
<Label label={login.string.ConfirmationSent} />
|
||||
</h4>
|
||||
<Label label={login.string.ConfirmationSent2} />
|
||||
</div>
|
||||
</div>
|
@ -17,11 +17,12 @@
|
||||
import { Status, Severity, OK, setMetadata } from '@hcengineering/platform'
|
||||
|
||||
import Form from './Form.svelte'
|
||||
import { createWorkspace } from '../utils'
|
||||
import { createWorkspace, getAccount } from '../utils'
|
||||
import { fetchMetadataLocalStorage, getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
|
||||
import login from '../plugin'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
const fields = [
|
||||
{
|
||||
@ -38,6 +39,16 @@
|
||||
|
||||
let status: Status<any> = OK
|
||||
|
||||
onMount(async () => {
|
||||
const account = await getAccount()
|
||||
if (account?.confirmed !== true) {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'confirmationSend'
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
}
|
||||
})
|
||||
|
||||
const action = {
|
||||
i18n: login.string.CreateWorkspace,
|
||||
func: async () => {
|
||||
|
@ -27,6 +27,8 @@
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import PasswordRequest from './PasswordRequest.svelte'
|
||||
import PasswordRestore from './PasswordRestore.svelte'
|
||||
import Confirmation from './Confirmation.svelte'
|
||||
import ConfirmationSend from './ConfirmationSend.svelte'
|
||||
|
||||
export let page: string = 'login'
|
||||
|
||||
@ -68,6 +70,10 @@
|
||||
<SelectWorkspace {navigateUrl} />
|
||||
{:else if page === 'join'}
|
||||
<Join />
|
||||
{:else if page === 'confirm'}
|
||||
<Confirmation />
|
||||
{:else if page === 'confirmationSend'}
|
||||
<ConfirmationSend />
|
||||
{/if}
|
||||
</div>
|
||||
<Intro landscape={$deviceInfo.docWidth <= 768} mini={$deviceInfo.docWidth <= 480} />
|
||||
|
@ -75,7 +75,7 @@
|
||||
}
|
||||
}
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'selectWorkspace'
|
||||
loc.path[1] = result.confirmed ? 'selectWorkspace' : 'confirmationSend'
|
||||
loc.path.length = 2
|
||||
if (navigateUrl !== undefined) {
|
||||
loc.query = { ...loc.query, navigateUrl }
|
||||
|
@ -26,14 +26,19 @@
|
||||
setMetadataLocalStorage
|
||||
} from '@hcengineering/ui'
|
||||
import login from '../plugin'
|
||||
import { getWorkspaces, navigateToWorkspace, selectWorkspace } from '../utils'
|
||||
import { getAccount, getWorkspaces, navigateToWorkspace, selectWorkspace } from '../utils'
|
||||
import StatusControl from './StatusControl.svelte'
|
||||
import { Workspace } from '@hcengineering/login'
|
||||
import { LoginInfo, Workspace } from '@hcengineering/login'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
export let navigateUrl: string | undefined = undefined
|
||||
|
||||
let status = OK
|
||||
|
||||
let account: LoginInfo | undefined = undefined
|
||||
|
||||
onMount(async () => (account = await getAccount()))
|
||||
|
||||
async function select (workspace: string) {
|
||||
status = new Status(Severity.INFO, login.status.ConnectingToServer, {})
|
||||
|
||||
@ -88,7 +93,7 @@
|
||||
{workspace.workspace}
|
||||
</div>
|
||||
{/each}
|
||||
{#if !workspaces.length}
|
||||
{#if !workspaces.length && account?.confirmed === true}
|
||||
<div class="form-row send">
|
||||
<Button label={login.string.CreateWorkspace} kind={'primary'} width="100%" on:click={createWorkspace} />
|
||||
</div>
|
||||
|
@ -14,13 +14,12 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Status, Severity, OK, setMetadata } from '@hcengineering/platform'
|
||||
import { OK, Severity, Status } from '@hcengineering/platform'
|
||||
|
||||
import Form from './Form.svelte'
|
||||
import { signUp } from '../utils'
|
||||
import { getCurrentLocation, navigate } from '@hcengineering/ui'
|
||||
import login from '../plugin'
|
||||
import { getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import { signUp } from '../utils'
|
||||
import Form from './Form.svelte'
|
||||
|
||||
const fields = [
|
||||
{ id: 'given-name', name: 'first', i18n: login.string.FirstName, short: true },
|
||||
@ -50,11 +49,8 @@
|
||||
status = loginStatus
|
||||
|
||||
if (result !== undefined) {
|
||||
setMetadata(presentation.metadata.Token, result.token)
|
||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
|
||||
setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'selectWorkspace'
|
||||
loc.path[1] = 'confirmationSend'
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
}
|
||||
|
@ -56,6 +56,8 @@ export default mergeIds(loginId, login, {
|
||||
RecoveryLinkSent: '' as IntlString,
|
||||
UseWorkspaceInviteSettings: '' as IntlString,
|
||||
GetLink: '' as IntlString,
|
||||
AlreadyJoined: '' as IntlString
|
||||
AlreadyJoined: '' as IntlString,
|
||||
ConfirmationSent: '' as IntlString,
|
||||
ConfirmationSent2: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -49,7 +49,7 @@ export async function doLogin (email: string, password: string): Promise<[Status
|
||||
if (token !== undefined) {
|
||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||
if (endpoint !== undefined) {
|
||||
return [OK, { token, endpoint, email }]
|
||||
return [OK, { token, endpoint, email, confirmed: true }]
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ export async function signUp (
|
||||
if (token !== undefined) {
|
||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||
if (endpoint !== undefined) {
|
||||
return [OK, { token, endpoint, email }]
|
||||
return [OK, { token, endpoint, email, confirmed: true }]
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,7 +127,7 @@ export async function createWorkspace (workspace: string): Promise<[Status, Logi
|
||||
if (overrideToken !== undefined) {
|
||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||
if (endpoint !== undefined) {
|
||||
return [OK, { token: overrideToken, endpoint, email }]
|
||||
return [OK, { token: overrideToken, endpoint, email, confirmed: true }]
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,6 +213,53 @@ export async function getWorkspaces (): Promise<Workspace[]> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAccount (): Promise<LoginInfo | undefined> {
|
||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||
|
||||
if (accountsUrl === undefined) {
|
||||
throw new Error('accounts url not specified')
|
||||
}
|
||||
|
||||
const overrideToken = getMetadata(login.metadata.OverrideLoginToken)
|
||||
if (overrideToken !== undefined) {
|
||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||
const email = fetchMetadataLocalStorage(login.metadata.LoginEmail) ?? ''
|
||||
if (endpoint !== undefined) {
|
||||
return { token: overrideToken, endpoint, email, confirmed: true }
|
||||
}
|
||||
}
|
||||
|
||||
const token = getMetadata(presentation.metadata.Token)
|
||||
if (token === undefined) {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'login'
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
return
|
||||
}
|
||||
|
||||
const request = {
|
||||
method: 'getAccountInfoByToken',
|
||||
params: [] as any[]
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
})
|
||||
const result = await response.json()
|
||||
if (result.error != null) {
|
||||
throw new PlatformError(result.error)
|
||||
}
|
||||
return result.result
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
export async function selectWorkspace (workspace: string): Promise<[Status, WorkspaceLoginInfo | undefined]> {
|
||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||
|
||||
@ -225,7 +272,7 @@ export async function selectWorkspace (workspace: string): Promise<[Status, Work
|
||||
if (overrideToken !== undefined) {
|
||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||
if (endpoint !== undefined) {
|
||||
return [OK, { token: overrideToken, endpoint, email, workspace }]
|
||||
return [OK, { token: overrideToken, endpoint, email, workspace, confirmed: true }]
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,7 +353,7 @@ export async function checkJoined (inviteId: string): Promise<[Status, Workspace
|
||||
if (overrideToken !== undefined) {
|
||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||
if (endpoint !== undefined) {
|
||||
return [OK, { token: overrideToken, endpoint, email, workspace: DEV_WORKSPACE }]
|
||||
return [OK, { token: overrideToken, endpoint, email, workspace: DEV_WORKSPACE, confirmed: true }]
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,7 +437,7 @@ export async function join (
|
||||
if (token !== undefined) {
|
||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||
if (endpoint !== undefined) {
|
||||
return [OK, { token, endpoint, email, workspace: DEV_WORKSPACE }]
|
||||
return [OK, { token, endpoint, email, workspace: DEV_WORKSPACE, confirmed: true }]
|
||||
}
|
||||
}
|
||||
|
||||
@ -431,7 +478,7 @@ export async function signUpJoin (
|
||||
if (token !== undefined) {
|
||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||
if (endpoint !== undefined) {
|
||||
return [OK, { token, endpoint, email, workspace: DEV_WORKSPACE }]
|
||||
return [OK, { token, endpoint, email, workspace: DEV_WORKSPACE, confirmed: true }]
|
||||
}
|
||||
}
|
||||
|
||||
@ -617,6 +664,40 @@ export async function requestPassword (email: string): Promise<Status> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function confirm (email: string): Promise<[Status, LoginInfo | undefined]> {
|
||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||
|
||||
if (accountsUrl === undefined) {
|
||||
throw new Error('accounts url not specified')
|
||||
}
|
||||
|
||||
const overrideToken = getMetadata(login.metadata.OverrideLoginToken)
|
||||
if (overrideToken !== undefined) {
|
||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||
if (endpoint !== undefined) {
|
||||
return [OK, { token: overrideToken, endpoint, email, confirmed: true }]
|
||||
}
|
||||
}
|
||||
const request = {
|
||||
method: 'confirm',
|
||||
params: [email]
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
})
|
||||
const result = await response.json()
|
||||
return [result.error ?? OK, result.result]
|
||||
} catch (err) {
|
||||
return [unknownError(err), undefined]
|
||||
}
|
||||
}
|
||||
|
||||
export async function restorePassword (token: string, password: string): Promise<[Status, LoginInfo | undefined]> {
|
||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||
|
||||
|
@ -42,6 +42,7 @@ export interface WorkspaceLoginInfo extends LoginInfo {
|
||||
export interface LoginInfo {
|
||||
token: string
|
||||
endpoint: string
|
||||
confirmed: boolean
|
||||
email: string
|
||||
}
|
||||
|
||||
|
@ -112,6 +112,7 @@ export interface Account {
|
||||
workspaces: ObjectId[]
|
||||
// Defined for server admins only
|
||||
admin?: boolean
|
||||
confirmed?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -222,6 +223,15 @@ async function getAccountInfo (db: Db, email: string, password: string): Promise
|
||||
return toAccountInfo(account)
|
||||
}
|
||||
|
||||
async function getAccountInfoByToken (db: Db, productId: string, token: string): Promise<AccountInfo> {
|
||||
const { email } = decodeToken(token)
|
||||
const account = await getAccount(db, email)
|
||||
if (account === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email }))
|
||||
}
|
||||
return toAccountInfo(account)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param db -
|
||||
@ -236,19 +246,22 @@ export async function login (db: Db, productId: string, email: string, password:
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
token: generateToken(email, getWorkspaceId('', productId), getAdminExtra(info))
|
||||
confirmed: info.confirmed ?? true,
|
||||
token: generateToken(email, getWorkspaceId('', productId), getExtra(info))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Will add admin=='true' in case of user is server admin
|
||||
* Will add extra props
|
||||
*/
|
||||
function getAdminExtra (
|
||||
info: Account | AccountInfo | null,
|
||||
rec?: Record<string, string>
|
||||
): Record<string, string> | undefined {
|
||||
return info?.admin === true ? { ...rec, admin: 'true' } : rec
|
||||
function getExtra (info: Account | AccountInfo | null, rec?: Record<string, any>): Record<string, any> | undefined {
|
||||
const res = rec ?? {}
|
||||
if (info?.admin === true) {
|
||||
res.admin = 'true'
|
||||
}
|
||||
res.confirmed = info?.confirmed ?? true
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
@ -270,7 +283,7 @@ export async function selectWorkspace (
|
||||
return {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
token: generateToken(email, getWorkspaceId(workspace, productId), getAdminExtra(accountInfo)),
|
||||
token: generateToken(email, getWorkspaceId(workspace, productId), getExtra(accountInfo)),
|
||||
workspace,
|
||||
productId
|
||||
}
|
||||
@ -286,7 +299,7 @@ export async function selectWorkspace (
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
token: generateToken(email, getWorkspaceId(workspace, productId), getAdminExtra(accountInfo)),
|
||||
token: generateToken(email, getWorkspaceId(workspace, productId), getExtra(accountInfo)),
|
||||
workspace,
|
||||
productId
|
||||
}
|
||||
@ -349,6 +362,80 @@ export async function join (
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function confirmEmail (db: Db, email: string): Promise<Account> {
|
||||
const account = await getAccount(db, email)
|
||||
|
||||
if (account === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: accountId }))
|
||||
}
|
||||
|
||||
await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { confirmed: true } })
|
||||
account.confirmed = true
|
||||
return account
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function confirm (db: Db, productId: string, token: string): Promise<LoginInfo> {
|
||||
const decode = decodeToken(token)
|
||||
const email = decode.extra?.confirm
|
||||
if (email === undefined) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: accountId }))
|
||||
}
|
||||
const account = await confirmEmail(db, email)
|
||||
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
token: generateToken(email, getWorkspaceId('', productId), getExtra(account))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function sendConfirmation (productId: string, account: Account): Promise<void> {
|
||||
const sesURL = getMetadata(accountPlugin.metadata.SES_URL)
|
||||
if (sesURL === undefined || sesURL === '') {
|
||||
throw new Error('Please provide email service url')
|
||||
}
|
||||
const front = getMetadata(accountPlugin.metadata.FrontURL)
|
||||
if (front === undefined || front === '') {
|
||||
throw new Error('Please provide front url')
|
||||
}
|
||||
|
||||
const token = generateToken(
|
||||
'@confirm',
|
||||
getWorkspaceId('', productId),
|
||||
getExtra(account, {
|
||||
confirm: account.email
|
||||
})
|
||||
)
|
||||
|
||||
const link = concatLink(front, `/login/confirm?id=${token}`)
|
||||
|
||||
const text = `To confirm your email, please paste the following link in your web browser's address bar: ${link}. If you did not make this request, please ignore this email.`
|
||||
const html = `<p>To confirm your email, please click the link below: <a href=${link}>Confirm Your Email</a></p><p>
|
||||
If the link above does not work, paste the following link in your web browser's address bar: ${link}
|
||||
</p><p>If you did not make this request, please ignore this email.</p>`
|
||||
const subject = 'Confirm Your Email'
|
||||
const to = account.email
|
||||
await fetch(concatLink(sesURL, '/send'), {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text,
|
||||
html,
|
||||
subject,
|
||||
to
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -363,7 +450,7 @@ export async function signUpJoin (
|
||||
): Promise<WorkspaceLoginInfo> {
|
||||
const invite = await getInvite(db, inviteId)
|
||||
const workspace = await checkInvite(invite, email)
|
||||
await createAccount(db, productId, email, password, first, last)
|
||||
await createAcc(db, productId, email, password, first, last, invite?.emailMask === email)
|
||||
await assignWorkspace(db, productId, email, workspace.name)
|
||||
|
||||
const token = (await login(db, productId, email, password)).token
|
||||
@ -372,17 +459,15 @@ export async function signUpJoin (
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createAccount (
|
||||
async function createAcc (
|
||||
db: Db,
|
||||
productId: string,
|
||||
email: string,
|
||||
password: string,
|
||||
first: string,
|
||||
last: string
|
||||
): Promise<LoginInfo> {
|
||||
last: string,
|
||||
confirmed: boolean = false
|
||||
): Promise<Account> {
|
||||
const salt = randomBytes(32)
|
||||
const hash = hashWithSalt(password, salt)
|
||||
|
||||
@ -402,13 +487,37 @@ export async function createAccount (
|
||||
salt,
|
||||
first,
|
||||
last,
|
||||
confirmed,
|
||||
workspaces: []
|
||||
})
|
||||
|
||||
const newAccount = await getAccount(db, email)
|
||||
if (newAccount === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountAlreadyExists, { account: email }))
|
||||
}
|
||||
if (!confirmed) {
|
||||
await sendConfirmation(productId, newAccount)
|
||||
}
|
||||
return newAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createAccount (
|
||||
db: Db,
|
||||
productId: string,
|
||||
email: string,
|
||||
password: string,
|
||||
first: string,
|
||||
last: string
|
||||
): Promise<LoginInfo> {
|
||||
const account = await createAcc(db, productId, email, password, first, last, false)
|
||||
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
token: generateToken(email, getWorkspaceId('', productId), getAdminExtra(account))
|
||||
token: generateToken(email, getWorkspaceId('', productId), getExtra(account))
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -493,7 +602,10 @@ export async function upgradeWorkspace (
|
||||
export const createUserWorkspace =
|
||||
(version: Data<Version>, txes: Tx[], migrationOperation: [string, MigrateOperation][]) =>
|
||||
async (db: Db, productId: string, token: string, workspace: string): Promise<LoginInfo> => {
|
||||
const { email } = decodeToken(token)
|
||||
const { email, extra } = decodeToken(token)
|
||||
if (extra?.confirmed === false) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email }))
|
||||
}
|
||||
await createWorkspace(version, txes, migrationOperation, db, productId, workspace, '')
|
||||
await assignWorkspace(db, productId, email, workspace)
|
||||
await setRole(email, workspace, productId, AccountRole.Owner)
|
||||
@ -501,7 +613,7 @@ export const createUserWorkspace =
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
token: generateToken(email, getWorkspaceId(workspace, productId), getAdminExtra(info)),
|
||||
token: generateToken(email, getWorkspaceId(workspace, productId), getExtra(info)),
|
||||
productId
|
||||
}
|
||||
return result
|
||||
@ -736,7 +848,7 @@ export async function requestPassword (db: Db, productId: string, email: string)
|
||||
const token = generateToken(
|
||||
'@restore',
|
||||
getWorkspaceId('', productId),
|
||||
getAdminExtra(account, {
|
||||
getExtra(account, {
|
||||
restore: email
|
||||
})
|
||||
)
|
||||
@ -1069,7 +1181,9 @@ export function getMethods (
|
||||
changePassword: wrap(changePassword),
|
||||
requestPassword: wrap(requestPassword),
|
||||
restorePassword: wrap(restorePassword),
|
||||
sendInvite: wrap(sendInvite)
|
||||
sendInvite: wrap(sendInvite),
|
||||
confirm: wrap(confirm),
|
||||
getAccountInfoByToken: wrap(getAccountInfoByToken)
|
||||
// updateAccount: wrap(updateAccount)
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import serverPlugin from './plugin'
|
||||
export interface Token {
|
||||
email: string
|
||||
workspace: WorkspaceId
|
||||
extra?: Record<string, string>
|
||||
extra?: Record<string, any>
|
||||
}
|
||||
|
||||
const getSecret = (): string => {
|
||||
|
@ -11,6 +11,7 @@ export SERVER_SECRET=secret
|
||||
node ../dev/tool/bundle.js create-workspace sanity-ws -o SanityTest
|
||||
# Create user record in accounts
|
||||
node ../dev/tool/bundle.js create-account user1 -f John -l Appleseed -p 1234
|
||||
node ../dev/tool/bundle.js confirm-email user1
|
||||
|
||||
|
||||
# Restore workspace contents in mongo/elastic
|
||||
|
@ -8,5 +8,6 @@ docker-compose -p sanity up -d --force-recreate --renew-anon-volumes
|
||||
./tool.sh create-workspace sanity-ws -o SanityTest
|
||||
# Create user record in accounts
|
||||
./tool.sh create-account user1 -f John -l Appleseed -p 1234
|
||||
./tool.sh confirm-email user1
|
||||
|
||||
./restore-workspace.sh
|
Loading…
Reference in New Issue
Block a user