UBERF-8429: Allow to disable sign-ups (#6934)
Some checks are pending
CI / build (push) Waiting to run
CI / svelte-check (push) Blocked by required conditions
CI / formatting (push) Blocked by required conditions
CI / test (push) Blocked by required conditions
CI / uitest (push) Waiting to run
CI / uitest-pg (push) Waiting to run
CI / uitest-qms (push) Waiting to run
CI / docker-build (push) Blocked by required conditions
CI / dist-build (push) Blocked by required conditions

Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
Alexey Zinoviev 2024-10-15 14:03:55 +04:00 committed by GitHub
parent 2c45db6c41
commit 6b112fc1be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 148 additions and 61 deletions

3
.vscode/launch.json vendored
View File

@ -97,7 +97,8 @@
"SES_URL": "",
"MINIO_ACCESS_KEY": "minioadmin",
"MINIO_SECRET_KEY": "minioadmin",
"MINIO_ENDPOINT": "localhost"
"MINIO_ENDPOINT": "localhost",
// "DISABLE_SIGNUP": "true",
// "INIT_SCRIPT_URL": "https://raw.githubusercontent.com/hcengineering/init/main/script.yaml",
// "INIT_WORKSPACE": "onboarding",
},

View File

@ -85,6 +85,7 @@ services:
- LAST_NAME_FIRST=true
- ACCOUNTS_URL=http://host.docker.internal:3000
- BRANDING_PATH=/var/cfg/branding.json
# - DISABLE_SIGNUP=true
# - INIT_SCRIPT_URL=https://raw.githubusercontent.com/hcengineering/init/main/script.yaml
# - INIT_WORKSPACE=onboarding
restart: unless-stopped
@ -197,6 +198,7 @@ services:
- DESKTOP_UPDATES_URL=https://dist.huly.io
- DESKTOP_UPDATES_CHANNEL=dev
- BRANDING_URL=http://host.docker.internal:8087/branding.json
# - DISABLE_SIGNUP=true
restart: unless-stopped
transactor:
image: hardcoreeng/transactor

View File

@ -57,6 +57,7 @@
export let ignoreInitialValidation: boolean = false
export let withProviders: boolean = false
export let subtitle: string | undefined = undefined
export let signUpDisabled = false
$: $themeStore.language && validate($themeStore.language)
@ -138,7 +139,7 @@
}}
>
{#if loginState !== 'none'}
<Tabs {loginState} />
<Tabs {loginState} {signUpDisabled} />
{:else}
{#if subtitle !== undefined}
<div class="fs-title">

View File

@ -54,6 +54,7 @@
export let page: Pages = 'signup'
const signUpDisabled = getMetadata(login.metadata.DisableSignUp) ?? false
let navigateUrl: string | undefined
onDestroy(location.subscribe(updatePageLoc))
@ -134,13 +135,13 @@
<Scroller padding={'1rem 0'}>
<div class="form-content">
{#if page === 'login'}
<LoginForm {navigateUrl} />
<LoginForm {navigateUrl} {signUpDisabled} />
{:else if page === 'signup'}
<SignupForm />
<SignupForm {signUpDisabled} />
{:else if page === 'createWorkspace'}
<CreateWorkspaceForm />
{:else if page === 'password'}
<PasswordRequest />
<PasswordRequest {signUpDisabled} />
{:else if page === 'recovery'}
<PasswordRestore />
{:else if page === 'selectWorkspace'}

View File

@ -21,6 +21,7 @@
import login from '../plugin'
export let navigateUrl: string | undefined = undefined
export let signUpDisabled = false
let method: LoginMethods = LoginMethods.Otp
@ -44,12 +45,12 @@
</script>
{#if method === LoginMethods.Otp}
<LoginOtpForm {navigateUrl} on:change={changeMethod} />
<LoginOtpForm {navigateUrl} {signUpDisabled} on:change={changeMethod} />
<div class="action">
<BottomActionComponent action={loginWithPasswordAction} />
</div>
{:else}
<LoginPasswordForm {navigateUrl} on:change={changeMethod} />
<LoginPasswordForm {navigateUrl} {signUpDisabled} on:change={changeMethod} />
<div class="action">
<BottomActionComponent action={loginWithCodeAction} />
</div>

View File

@ -21,6 +21,7 @@
import { OtpLoginSteps, sendOtp } from '../index'
export let navigateUrl: string | undefined = undefined
export let signUpDisabled = false
const fields = [{ id: 'email', name: 'username', i18n: login.string.Email }]
const formData = {
@ -57,11 +58,12 @@
{fields}
object={formData}
{action}
{signUpDisabled}
ignoreInitialValidation
withProviders
/>
{/if}
{#if step === OtpLoginSteps.Otp && formData.username !== ''}
<OtpForm email={formData.username} {navigateUrl} retryOn={otpRetryOn} on:step={handleStep} />
<OtpForm email={formData.username} {signUpDisabled} {navigateUrl} retryOn={otpRetryOn} on:step={handleStep} />
{/if}

View File

@ -21,6 +21,7 @@
import login from '../plugin'
export let navigateUrl: string | undefined = undefined
export let signUpDisabled = false
const fields = [
{ id: 'email', name: 'username', i18n: login.string.Email },
@ -62,6 +63,7 @@
{fields}
{object}
{action}
{signUpDisabled}
bottomActions={[recoveryAction]}
ignoreInitialValidation
withProviders

View File

@ -28,6 +28,7 @@
export let navigateUrl: string | undefined = undefined
export let email: string
export let retryOn: Timestamp
export let signUpDisabled = false
const dispatch = createEventDispatcher()
@ -232,7 +233,7 @@
style:min-height={$deviceInfo.docHeight > 720 ? '42rem' : '0'}
>
<div class="header">
<Tabs loginState="login" />
<Tabs loginState="login" {signUpDisabled} />
<div class="description">
<Label label={login.string.SentTo} />
<span class="email ml-1">

View File

@ -23,8 +23,9 @@
import { BottomAction } from '..'
import { signUpAction } from '../actions'
const fields = [{ id: 'email', name: 'username', i18n: login.string.Email }]
export let signUpDisabled = false
const fields = [{ id: 'email', name: 'username', i18n: login.string.Email }]
const object = {
username: ''
}
@ -64,7 +65,7 @@
goTo('login')
}
},
signUpAction
...(signUpDisabled ? [] : [signUpAction])
]
</script>

View File

@ -21,6 +21,8 @@
import { goTo, signUp } from '../utils'
import Form from './Form.svelte'
export let signUpDisabled = false
const fields = [
{ id: 'given-name', name: 'first', i18n: login.string.FirstName, short: true },
{ id: 'family-name', name: 'last', i18n: login.string.LastName, short: true },
@ -39,6 +41,10 @@
let status: Status<any> = OK
if (signUpDisabled) {
goTo('login')
}
const action = {
i18n: login.string.SignUp,
func: async () => {

View File

@ -19,6 +19,7 @@
import login from '../plugin'
export let loginState: 'login' | 'signup' | 'none' = 'none'
export let signUpDisabled = false
const goTab = (path: string) => {
const loc = getCurrentLocation()
@ -29,16 +30,18 @@
</script>
<div class="flex-row-center caption">
<a
class="title"
class:selected={loginState === 'signup'}
href="."
on:click|preventDefault={() => {
if (loginState !== 'signup') goTab('signup')
}}
>
<Label label={login.string.SignUp} />
</a>
{#if !signUpDisabled}
<a
class="title"
class:selected={loginState === 'signup'}
href="."
on:click|preventDefault={() => {
if (loginState !== 'signup') goTab('signup')
}}
>
<Label label={login.string.SignUp} />
</a>
{/if}
<a
class="title"
class:selected={loginState === 'login'}

View File

@ -72,7 +72,8 @@ export default plugin(loginId, {
LoginTokens: '' as Metadata<Record<string, string>>,
LastToken: '' as Metadata<string>,
LoginEndpoint: '' as Metadata<string>,
LoginEmail: '' as Metadata<string>
LoginEmail: '' as Metadata<string>,
DisableSignUp: '' as Metadata<boolean>
},
component: {
LoginApp: '' as AnyComponent,

View File

@ -13,7 +13,8 @@ export function registerGithub (
accountsUrl: string,
dbPromise: Promise<AccountDB>,
frontUrl: string,
brandings: BrandingMap
brandings: BrandingMap,
signUpDisabled?: boolean
): string | undefined {
const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID
const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET
@ -69,7 +70,7 @@ export function registerGithub (
const [first, last] = ctx.state.user.displayName?.split(' ') ?? [ctx.state.user.username, '']
measureCtx.info('Provider auth handler', { email, type: 'github' })
if (email !== undefined) {
let loginInfo: LoginInfo
let loginInfo: LoginInfo | null
const state = safeParseAuthState(ctx.query?.state)
const branding = getBranding(brandings, state?.branding)
const db = await dbPromise
@ -78,17 +79,35 @@ export function registerGithub (
githubId: ctx.state.user.id
})
} else {
loginInfo = await loginWithProvider(measureCtx, db, null, email, first, last, {
githubId: ctx.state.user.id
})
loginInfo = await loginWithProvider(
measureCtx,
db,
null,
email,
first,
last,
{
githubId: ctx.state.user.id
},
signUpDisabled
)
}
const origin = concatLink(branding?.front ?? frontUrl, '/login/auth')
const query = encodeURIComponent(qs.stringify({ token: loginInfo.token }))
if (loginInfo === null) {
measureCtx.info('Failed to auth: no associated account found', {
email,
type: 'github',
user: ctx.state?.user
})
ctx.redirect(concatLink(branding?.front ?? frontUrl, '/login'))
} else {
const origin = concatLink(branding?.front ?? frontUrl, '/login/auth')
const query = encodeURIComponent(qs.stringify({ token: loginInfo.token }))
measureCtx.info('Success auth, redirect', { email, type: 'github', target: origin })
// Successful authentication, redirect to your application
ctx.redirect(`${origin}?${query}`)
measureCtx.info('Success auth, redirect', { email, type: 'github', target: origin })
// Successful authentication, redirect to your application
ctx.redirect(`${origin}?${query}`)
}
}
} catch (err: any) {
measureCtx.error('failed to auth', { err, type: 'github', user: ctx.state?.user })

View File

@ -13,7 +13,8 @@ export function registerGoogle (
accountsUrl: string,
dbPromise: Promise<AccountDB>,
frontUrl: string,
brandings: BrandingMap
brandings: BrandingMap,
signUpDisabled?: boolean
): string | undefined {
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID
const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET
@ -70,22 +71,31 @@ export function registerGoogle (
measureCtx.info('Provider auth handler', { email, type: 'google' })
if (email !== undefined) {
try {
let loginInfo: LoginInfo
let loginInfo: LoginInfo | null
const state = safeParseAuthState(ctx.query?.state)
const branding = getBranding(brandings, state?.branding)
const db = await dbPromise
if (state.inviteId != null && state.inviteId !== '') {
loginInfo = await joinWithProvider(measureCtx, db, null, email, first, last, state.inviteId as any)
} else {
loginInfo = await loginWithProvider(measureCtx, db, null, email, first, last)
loginInfo = await loginWithProvider(measureCtx, db, null, email, first, last, undefined, signUpDisabled)
}
const origin = concatLink(branding?.front ?? frontUrl, '/login/auth')
const query = encodeURIComponent(qs.stringify({ token: loginInfo.token }))
if (loginInfo === null) {
measureCtx.info('Failed to auth: no associated account found', {
email,
type: 'google',
user: ctx.state?.user
})
ctx.redirect(concatLink(branding?.front ?? frontUrl, '/login'))
} else {
const origin = concatLink(branding?.front ?? frontUrl, '/login/auth')
const query = encodeURIComponent(qs.stringify({ token: loginInfo.token }))
// Successful authentication, redirect to your application
measureCtx.info('Success auth, redirect', { email, type: 'google', target: origin })
ctx.redirect(`${origin}?${query}`)
// Successful authentication, redirect to your application
measureCtx.info('Success auth, redirect', { email, type: 'google', target: origin })
ctx.redirect(`${origin}?${query}`)
}
} catch (err: any) {
measureCtx.error('failed to auth', { err, type: 'google', user: ctx.state?.user })
}

View File

@ -18,7 +18,8 @@ export type AuthProvider = (
accountsUrl: string,
db: Promise<AccountDB>,
frontUrl: string,
brandings: BrandingMap
brandings: BrandingMap,
signUpDisabled?: boolean
) => string | undefined
export function registerProviders (
@ -28,7 +29,8 @@ export function registerProviders (
db: Promise<AccountDB>,
serverSecret: string,
frontUrl: string | undefined,
brandings: BrandingMap
brandings: BrandingMap,
signUpDisabled: boolean = false
): void {
const accountsUrl = process.env.ACCOUNTS_URL
if (accountsUrl === undefined) {
@ -63,7 +65,7 @@ export function registerProviders (
const res: string[] = []
const providers: AuthProvider[] = [registerGoogle, registerGithub, registerOpenid]
for (const provider of providers) {
const value = provider(ctx, passport, router, accountsUrl, db, frontUrl, brandings)
const value = provider(ctx, passport, router, accountsUrl, db, frontUrl, brandings, signUpDisabled)
if (value !== undefined) res.push(value)
}

View File

@ -28,7 +28,8 @@ export function registerOpenid (
accountsUrl: string,
dbPromise: Promise<AccountDB>,
frontUrl: string,
brandings: BrandingMap
brandings: BrandingMap,
signUpDisabled?: boolean
): string | undefined {
const openidClientId = process.env.OPENID_CLIENT_ID
const openidClientSecret = process.env.OPENID_CLIENT_SECRET
@ -90,7 +91,7 @@ export function registerOpenid (
const [first, last] = ctx.state.user.name?.split(' ') ?? [ctx.state.user.username, '']
measureCtx.info('Provider auth handler', { email, type: 'openid' })
if (email !== undefined) {
let loginInfo: LoginInfo
let loginInfo: LoginInfo | null
const state = safeParseAuthState(ctx.query?.state)
const branding = getBranding(brandings, state?.branding)
const db = await dbPromise
@ -99,17 +100,35 @@ export function registerOpenid (
openId: ctx.state.user.sub
})
} else {
loginInfo = await loginWithProvider(measureCtx, db, null, email, first, last, {
openId: ctx.state.user.sub
})
loginInfo = await loginWithProvider(
measureCtx,
db,
null,
email,
first,
last,
{
openId: ctx.state.user.sub
},
signUpDisabled
)
}
const origin = concatLink(branding?.front ?? frontUrl, '/login/auth')
const query = encodeURIComponent(qs.stringify({ token: loginInfo.token }))
if (loginInfo === null) {
measureCtx.info('Failed to auth: no associated account found', {
email,
type: 'openid',
user: ctx.state?.user
})
ctx.redirect(concatLink(branding?.front ?? frontUrl, '/login'))
} else {
const origin = concatLink(branding?.front ?? frontUrl, '/login/auth')
const query = encodeURIComponent(qs.stringify({ token: loginInfo.token }))
measureCtx.info('Success auth, redirect', { email, type: 'openid', target: origin })
// Successful authentication, redirect to your application
ctx.redirect(`${origin}?${query}`)
measureCtx.info('Success auth, redirect', { email, type: 'openid', target: origin })
// Successful authentication, redirect to your application
ctx.redirect(`${origin}?${query}`)
}
}
} catch (err: any) {
measureCtx.error('failed to auth', { err, type: 'openid', user: ctx.state?.user })

View File

@ -13,7 +13,8 @@ export function registerToken (
accountsUrl: string,
dbPromise: Promise<AccountDB>,
frontUrl: string,
brandings: BrandingMap
brandings: BrandingMap,
signUpDisabled?: boolean
): string | undefined {
passport.use(
'token',

View File

@ -30,7 +30,6 @@ import os from 'os'
*/
export function serveAccount (measureCtx: MeasureContext, brandings: BrandingMap, onClose?: () => void): void {
console.log('Starting account service with brandings: ', brandings)
const methods = getMethods()
const ACCOUNT_PORT = parseInt(process.env.ACCOUNT_PORT ?? '3000')
const dbUrl = process.env.DB_URL
if (dbUrl === undefined) {
@ -81,6 +80,9 @@ export function serveAccount (measureCtx: MeasureContext, brandings: BrandingMap
setMetadata(toolPlugin.metadata.InitScriptURL, initScriptUrl)
}
const hasSignUp = process.env.DISABLE_SIGNUP !== 'true'
const methods = getMethods(hasSignUp)
const accountsDb = getAccountDB(dbUrl)
const app = new Koa()
@ -105,7 +107,8 @@ export function serveAccount (measureCtx: MeasureContext, brandings: BrandingMap
}),
serverSecret,
frontURL,
brandings
brandings,
!hasSignUp
)
void accountsDb.then((res) => {

View File

@ -2334,8 +2334,9 @@ export async function loginWithProvider (
_email: string,
first: string,
last: string,
extra?: Record<string, string>
): Promise<LoginInfo> {
extra?: Record<string, string>,
signUpDisabled: boolean = false
): Promise<LoginInfo | null> {
try {
const email = cleanEmail(_email)
if (last == null) {
@ -2357,6 +2358,11 @@ export async function loginWithProvider (
}
return result
}
if (signUpDisabled) {
return null
}
const newAccount = await createAcc(ctx, db, branding, email, null, first, last, true, true, extra)
const result = {
@ -2463,7 +2469,7 @@ export async function deleteWorkspace (
/**
* @public
*/
export function getMethods (): Record<string, AccountMethod> {
export function getMethods (hasSignUp: boolean = true): Record<string, AccountMethod> {
return {
login: wrap(login),
join: wrap(join),
@ -2478,7 +2484,7 @@ export function getMethods (): Record<string, AccountMethod> {
getInviteLink: wrap(getInviteLink),
getAccountInfo: wrap(getAccountInfo),
getWorkspaceInfo: wrap(getWorkspaceInfo),
createAccount: wrap(createAccount),
...(hasSignUp ? { createAccount: wrap(createAccount) } : {}),
createWorkspace: wrap(createUserWorkspace),
assignWorkspace: wrap(assignWorkspace),
removeWorkspace: wrap(removeWorkspace),

View File

@ -257,6 +257,7 @@ export function start (
brandingUrl?: string
previewConfig: string
pushPublicKey?: string
disableSignUp?: string
},
port: number,
extraConfig?: Record<string, string | undefined>
@ -308,6 +309,7 @@ export function start (
BRANDING_URL: config.brandingUrl,
PREVIEW_CONFIG: config.previewConfig,
PUSH_PUBLIC_KEY: config.pushPublicKey,
DISABLE_SIGNUP: config.disableSignUp,
...(extraConfig ?? {})
}
res.status(200)

View File

@ -118,6 +118,8 @@ export function startFront (ctx: MeasureContext, extraConfig?: Record<string, st
setMetadata(serverToken.metadata.Secret, serverSecret)
const disableSignUp = process.env.DISABLE_SIGNUP
const config = {
elasticUrl,
storageAdapter,
@ -134,7 +136,8 @@ export function startFront (ctx: MeasureContext, extraConfig?: Record<string, st
collaborator,
brandingUrl,
previewConfig,
pushPublicKey
pushPublicKey,
disableSignUp
}
console.log('Starting Front service with', config)
const shutdown = start(ctx, config, SERVER_PORT, extraConfig)