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 {
|
import {
|
||||||
ACCOUNT_DB,
|
ACCOUNT_DB,
|
||||||
assignWorkspace,
|
assignWorkspace,
|
||||||
|
confirmEmail,
|
||||||
createAccount,
|
createAccount,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
dropAccount,
|
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
|
program
|
||||||
.command('diff-workspace <workspace>')
|
.command('diff-workspace <workspace>')
|
||||||
.description('restore workspace transactions and minio resources from previous dump.')
|
.description('restore workspace transactions and minio resources from previous dump.')
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
import { popupstore as modal } from '../popups'
|
import { popupstore as modal } from '../popups'
|
||||||
import PopupInstance from './PopupInstance.svelte'
|
import PopupInstance from './PopupInstance.svelte'
|
||||||
|
|
||||||
export let contentPanel: HTMLElement
|
export let contentPanel: HTMLElement | undefined = undefined
|
||||||
|
|
||||||
const instances: PopupInstance[] = []
|
const instances: PopupInstance[] = []
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
export let zIndex: number
|
export let zIndex: number
|
||||||
export let top: boolean
|
export let top: boolean
|
||||||
export let close: () => void
|
export let close: () => void
|
||||||
export let contentPanel: HTMLElement
|
export let contentPanel: HTMLElement | undefined
|
||||||
|
|
||||||
let modalHTML: HTMLElement
|
let modalHTML: HTMLElement
|
||||||
let componentInstance: any
|
let componentInstance: any
|
||||||
@ -68,7 +68,11 @@
|
|||||||
_close(undefined)
|
_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')) {
|
if ((fullSize || docSize) && (element === 'float' || element === 'centered')) {
|
||||||
options = fitPopupElement(modalHTML, 'full', contentPanel)
|
options = fitPopupElement(modalHTML, 'full', contentPanel)
|
||||||
options.props.maxHeight = '100vh'
|
options.props.maxHeight = '100vh'
|
||||||
|
@ -39,6 +39,8 @@
|
|||||||
"InviteLimit": "Invite limit:",
|
"InviteLimit": "Invite limit:",
|
||||||
"GetLink": "Get invite link",
|
"GetLink": "Get invite link",
|
||||||
"NoLimit": "No limit",
|
"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": "Предел использований:",
|
"InviteLimit": "Предел использований:",
|
||||||
"GetLink": "Получить ссылку",
|
"GetLink": "Получить ссылку",
|
||||||
"NoLimit": "Без предела использований",
|
"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 { Status, Severity, OK, setMetadata } from '@hcengineering/platform'
|
||||||
|
|
||||||
import Form from './Form.svelte'
|
import Form from './Form.svelte'
|
||||||
import { createWorkspace } from '../utils'
|
import { createWorkspace, getAccount } from '../utils'
|
||||||
import { fetchMetadataLocalStorage, getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
|
import { fetchMetadataLocalStorage, getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
|
||||||
import login from '../plugin'
|
import login from '../plugin'
|
||||||
import { workbenchId } from '@hcengineering/workbench'
|
import { workbenchId } from '@hcengineering/workbench'
|
||||||
import presentation from '@hcengineering/presentation'
|
import presentation from '@hcengineering/presentation'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
{
|
{
|
||||||
@ -38,6 +39,16 @@
|
|||||||
|
|
||||||
let status: Status<any> = OK
|
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 = {
|
const action = {
|
||||||
i18n: login.string.CreateWorkspace,
|
i18n: login.string.CreateWorkspace,
|
||||||
func: async () => {
|
func: async () => {
|
||||||
|
@ -27,6 +27,8 @@
|
|||||||
import { getMetadata } from '@hcengineering/platform'
|
import { getMetadata } from '@hcengineering/platform'
|
||||||
import PasswordRequest from './PasswordRequest.svelte'
|
import PasswordRequest from './PasswordRequest.svelte'
|
||||||
import PasswordRestore from './PasswordRestore.svelte'
|
import PasswordRestore from './PasswordRestore.svelte'
|
||||||
|
import Confirmation from './Confirmation.svelte'
|
||||||
|
import ConfirmationSend from './ConfirmationSend.svelte'
|
||||||
|
|
||||||
export let page: string = 'login'
|
export let page: string = 'login'
|
||||||
|
|
||||||
@ -68,6 +70,10 @@
|
|||||||
<SelectWorkspace {navigateUrl} />
|
<SelectWorkspace {navigateUrl} />
|
||||||
{:else if page === 'join'}
|
{:else if page === 'join'}
|
||||||
<Join />
|
<Join />
|
||||||
|
{:else if page === 'confirm'}
|
||||||
|
<Confirmation />
|
||||||
|
{:else if page === 'confirmationSend'}
|
||||||
|
<ConfirmationSend />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Intro landscape={$deviceInfo.docWidth <= 768} mini={$deviceInfo.docWidth <= 480} />
|
<Intro landscape={$deviceInfo.docWidth <= 768} mini={$deviceInfo.docWidth <= 480} />
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const loc = getCurrentLocation()
|
const loc = getCurrentLocation()
|
||||||
loc.path[1] = 'selectWorkspace'
|
loc.path[1] = result.confirmed ? 'selectWorkspace' : 'confirmationSend'
|
||||||
loc.path.length = 2
|
loc.path.length = 2
|
||||||
if (navigateUrl !== undefined) {
|
if (navigateUrl !== undefined) {
|
||||||
loc.query = { ...loc.query, navigateUrl }
|
loc.query = { ...loc.query, navigateUrl }
|
||||||
|
@ -26,14 +26,19 @@
|
|||||||
setMetadataLocalStorage
|
setMetadataLocalStorage
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import login from '../plugin'
|
import login from '../plugin'
|
||||||
import { getWorkspaces, navigateToWorkspace, selectWorkspace } from '../utils'
|
import { getAccount, getWorkspaces, navigateToWorkspace, selectWorkspace } from '../utils'
|
||||||
import StatusControl from './StatusControl.svelte'
|
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
|
export let navigateUrl: string | undefined = undefined
|
||||||
|
|
||||||
let status = OK
|
let status = OK
|
||||||
|
|
||||||
|
let account: LoginInfo | undefined = undefined
|
||||||
|
|
||||||
|
onMount(async () => (account = await getAccount()))
|
||||||
|
|
||||||
async function select (workspace: string) {
|
async function select (workspace: string) {
|
||||||
status = new Status(Severity.INFO, login.status.ConnectingToServer, {})
|
status = new Status(Severity.INFO, login.status.ConnectingToServer, {})
|
||||||
|
|
||||||
@ -88,7 +93,7 @@
|
|||||||
{workspace.workspace}
|
{workspace.workspace}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{#if !workspaces.length}
|
{#if !workspaces.length && account?.confirmed === true}
|
||||||
<div class="form-row send">
|
<div class="form-row send">
|
||||||
<Button label={login.string.CreateWorkspace} kind={'primary'} width="100%" on:click={createWorkspace} />
|
<Button label={login.string.CreateWorkspace} kind={'primary'} width="100%" on:click={createWorkspace} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,13 +14,12 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Status, Severity, OK, setMetadata } from '@hcengineering/platform'
|
import { OK, Severity, Status } from '@hcengineering/platform'
|
||||||
|
|
||||||
import Form from './Form.svelte'
|
import { getCurrentLocation, navigate } from '@hcengineering/ui'
|
||||||
import { signUp } from '../utils'
|
|
||||||
import login from '../plugin'
|
import login from '../plugin'
|
||||||
import { getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
|
import { signUp } from '../utils'
|
||||||
import presentation from '@hcengineering/presentation'
|
import Form from './Form.svelte'
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
{ id: 'given-name', name: 'first', i18n: login.string.FirstName, short: true },
|
{ id: 'given-name', name: 'first', i18n: login.string.FirstName, short: true },
|
||||||
@ -50,11 +49,8 @@
|
|||||||
status = loginStatus
|
status = loginStatus
|
||||||
|
|
||||||
if (result !== undefined) {
|
if (result !== undefined) {
|
||||||
setMetadata(presentation.metadata.Token, result.token)
|
|
||||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
|
|
||||||
setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
|
|
||||||
const loc = getCurrentLocation()
|
const loc = getCurrentLocation()
|
||||||
loc.path[1] = 'selectWorkspace'
|
loc.path[1] = 'confirmationSend'
|
||||||
loc.path.length = 2
|
loc.path.length = 2
|
||||||
navigate(loc)
|
navigate(loc)
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,8 @@ export default mergeIds(loginId, login, {
|
|||||||
RecoveryLinkSent: '' as IntlString,
|
RecoveryLinkSent: '' as IntlString,
|
||||||
UseWorkspaceInviteSettings: '' as IntlString,
|
UseWorkspaceInviteSettings: '' as IntlString,
|
||||||
GetLink: '' 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) {
|
if (token !== undefined) {
|
||||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||||
if (endpoint !== undefined) {
|
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) {
|
if (token !== undefined) {
|
||||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||||
if (endpoint !== undefined) {
|
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) {
|
if (overrideToken !== undefined) {
|
||||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||||
if (endpoint !== undefined) {
|
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]> {
|
export async function selectWorkspace (workspace: string): Promise<[Status, WorkspaceLoginInfo | undefined]> {
|
||||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||||
|
|
||||||
@ -225,7 +272,7 @@ export async function selectWorkspace (workspace: string): Promise<[Status, Work
|
|||||||
if (overrideToken !== undefined) {
|
if (overrideToken !== undefined) {
|
||||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||||
if (endpoint !== undefined) {
|
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) {
|
if (overrideToken !== undefined) {
|
||||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||||
if (endpoint !== undefined) {
|
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) {
|
if (token !== undefined) {
|
||||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||||
if (endpoint !== undefined) {
|
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) {
|
if (token !== undefined) {
|
||||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||||
if (endpoint !== undefined) {
|
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]> {
|
export async function restorePassword (token: string, password: string): Promise<[Status, LoginInfo | undefined]> {
|
||||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ export interface WorkspaceLoginInfo extends LoginInfo {
|
|||||||
export interface LoginInfo {
|
export interface LoginInfo {
|
||||||
token: string
|
token: string
|
||||||
endpoint: string
|
endpoint: string
|
||||||
|
confirmed: boolean
|
||||||
email: string
|
email: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +112,7 @@ export interface Account {
|
|||||||
workspaces: ObjectId[]
|
workspaces: ObjectId[]
|
||||||
// Defined for server admins only
|
// Defined for server admins only
|
||||||
admin?: boolean
|
admin?: boolean
|
||||||
|
confirmed?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -222,6 +223,15 @@ async function getAccountInfo (db: Db, email: string, password: string): Promise
|
|||||||
return toAccountInfo(account)
|
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
|
* @public
|
||||||
* @param db -
|
* @param db -
|
||||||
@ -236,19 +246,22 @@ export async function login (db: Db, productId: string, email: string, password:
|
|||||||
const result = {
|
const result = {
|
||||||
endpoint: getEndpoint(),
|
endpoint: getEndpoint(),
|
||||||
email,
|
email,
|
||||||
token: generateToken(email, getWorkspaceId('', productId), getAdminExtra(info))
|
confirmed: info.confirmed ?? true,
|
||||||
|
token: generateToken(email, getWorkspaceId('', productId), getExtra(info))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will add admin=='true' in case of user is server admin
|
* Will add extra props
|
||||||
*/
|
*/
|
||||||
function getAdminExtra (
|
function getExtra (info: Account | AccountInfo | null, rec?: Record<string, any>): Record<string, any> | undefined {
|
||||||
info: Account | AccountInfo | null,
|
const res = rec ?? {}
|
||||||
rec?: Record<string, string>
|
if (info?.admin === true) {
|
||||||
): Record<string, string> | undefined {
|
res.admin = 'true'
|
||||||
return info?.admin === true ? { ...rec, admin: 'true' } : rec
|
}
|
||||||
|
res.confirmed = info?.confirmed ?? true
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -270,7 +283,7 @@ export async function selectWorkspace (
|
|||||||
return {
|
return {
|
||||||
endpoint: getEndpoint(),
|
endpoint: getEndpoint(),
|
||||||
email,
|
email,
|
||||||
token: generateToken(email, getWorkspaceId(workspace, productId), getAdminExtra(accountInfo)),
|
token: generateToken(email, getWorkspaceId(workspace, productId), getExtra(accountInfo)),
|
||||||
workspace,
|
workspace,
|
||||||
productId
|
productId
|
||||||
}
|
}
|
||||||
@ -286,7 +299,7 @@ export async function selectWorkspace (
|
|||||||
const result = {
|
const result = {
|
||||||
endpoint: getEndpoint(),
|
endpoint: getEndpoint(),
|
||||||
email,
|
email,
|
||||||
token: generateToken(email, getWorkspaceId(workspace, productId), getAdminExtra(accountInfo)),
|
token: generateToken(email, getWorkspaceId(workspace, productId), getExtra(accountInfo)),
|
||||||
workspace,
|
workspace,
|
||||||
productId
|
productId
|
||||||
}
|
}
|
||||||
@ -349,6 +362,80 @@ export async function join (
|
|||||||
return result
|
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
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -363,7 +450,7 @@ export async function signUpJoin (
|
|||||||
): Promise<WorkspaceLoginInfo> {
|
): Promise<WorkspaceLoginInfo> {
|
||||||
const invite = await getInvite(db, inviteId)
|
const invite = await getInvite(db, inviteId)
|
||||||
const workspace = await checkInvite(invite, email)
|
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)
|
await assignWorkspace(db, productId, email, workspace.name)
|
||||||
|
|
||||||
const token = (await login(db, productId, email, password)).token
|
const token = (await login(db, productId, email, password)).token
|
||||||
@ -372,17 +459,15 @@ export async function signUpJoin (
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function createAcc (
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export async function createAccount (
|
|
||||||
db: Db,
|
db: Db,
|
||||||
productId: string,
|
productId: string,
|
||||||
email: string,
|
email: string,
|
||||||
password: string,
|
password: string,
|
||||||
first: string,
|
first: string,
|
||||||
last: string
|
last: string,
|
||||||
): Promise<LoginInfo> {
|
confirmed: boolean = false
|
||||||
|
): Promise<Account> {
|
||||||
const salt = randomBytes(32)
|
const salt = randomBytes(32)
|
||||||
const hash = hashWithSalt(password, salt)
|
const hash = hashWithSalt(password, salt)
|
||||||
|
|
||||||
@ -402,13 +487,37 @@ export async function createAccount (
|
|||||||
salt,
|
salt,
|
||||||
first,
|
first,
|
||||||
last,
|
last,
|
||||||
|
confirmed,
|
||||||
workspaces: []
|
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 = {
|
const result = {
|
||||||
endpoint: getEndpoint(),
|
endpoint: getEndpoint(),
|
||||||
email,
|
email,
|
||||||
token: generateToken(email, getWorkspaceId('', productId), getAdminExtra(account))
|
token: generateToken(email, getWorkspaceId('', productId), getExtra(account))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -493,7 +602,10 @@ export async function upgradeWorkspace (
|
|||||||
export const createUserWorkspace =
|
export const createUserWorkspace =
|
||||||
(version: Data<Version>, txes: Tx[], migrationOperation: [string, MigrateOperation][]) =>
|
(version: Data<Version>, txes: Tx[], migrationOperation: [string, MigrateOperation][]) =>
|
||||||
async (db: Db, productId: string, token: string, workspace: string): Promise<LoginInfo> => {
|
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 createWorkspace(version, txes, migrationOperation, db, productId, workspace, '')
|
||||||
await assignWorkspace(db, productId, email, workspace)
|
await assignWorkspace(db, productId, email, workspace)
|
||||||
await setRole(email, workspace, productId, AccountRole.Owner)
|
await setRole(email, workspace, productId, AccountRole.Owner)
|
||||||
@ -501,7 +613,7 @@ export const createUserWorkspace =
|
|||||||
const result = {
|
const result = {
|
||||||
endpoint: getEndpoint(),
|
endpoint: getEndpoint(),
|
||||||
email,
|
email,
|
||||||
token: generateToken(email, getWorkspaceId(workspace, productId), getAdminExtra(info)),
|
token: generateToken(email, getWorkspaceId(workspace, productId), getExtra(info)),
|
||||||
productId
|
productId
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@ -736,7 +848,7 @@ export async function requestPassword (db: Db, productId: string, email: string)
|
|||||||
const token = generateToken(
|
const token = generateToken(
|
||||||
'@restore',
|
'@restore',
|
||||||
getWorkspaceId('', productId),
|
getWorkspaceId('', productId),
|
||||||
getAdminExtra(account, {
|
getExtra(account, {
|
||||||
restore: email
|
restore: email
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -1069,7 +1181,9 @@ export function getMethods (
|
|||||||
changePassword: wrap(changePassword),
|
changePassword: wrap(changePassword),
|
||||||
requestPassword: wrap(requestPassword),
|
requestPassword: wrap(requestPassword),
|
||||||
restorePassword: wrap(restorePassword),
|
restorePassword: wrap(restorePassword),
|
||||||
sendInvite: wrap(sendInvite)
|
sendInvite: wrap(sendInvite),
|
||||||
|
confirm: wrap(confirm),
|
||||||
|
getAccountInfoByToken: wrap(getAccountInfoByToken)
|
||||||
// updateAccount: wrap(updateAccount)
|
// updateAccount: wrap(updateAccount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import serverPlugin from './plugin'
|
|||||||
export interface Token {
|
export interface Token {
|
||||||
email: string
|
email: string
|
||||||
workspace: WorkspaceId
|
workspace: WorkspaceId
|
||||||
extra?: Record<string, string>
|
extra?: Record<string, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSecret = (): string => {
|
const getSecret = (): string => {
|
||||||
|
@ -11,6 +11,7 @@ export SERVER_SECRET=secret
|
|||||||
node ../dev/tool/bundle.js create-workspace sanity-ws -o SanityTest
|
node ../dev/tool/bundle.js create-workspace sanity-ws -o SanityTest
|
||||||
# Create user record in accounts
|
# Create user record in accounts
|
||||||
node ../dev/tool/bundle.js create-account user1 -f John -l Appleseed -p 1234
|
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
|
# 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
|
./tool.sh create-workspace sanity-ws -o SanityTest
|
||||||
# Create user record in accounts
|
# Create user record in accounts
|
||||||
./tool.sh create-account user1 -f John -l Appleseed -p 1234
|
./tool.sh create-account user1 -f John -l Appleseed -p 1234
|
||||||
|
./tool.sh confirm-email user1
|
||||||
|
|
||||||
./restore-workspace.sh
|
./restore-workspace.sh
|
Loading…
Reference in New Issue
Block a user