From c680dfd92100160fedfa3e6f7a7aa66c8e081377 Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Mon, 20 Nov 2023 21:58:36 +0100 Subject: [PATCH] feat: add session timeout configs (#18642) * feat: add session timeout configs * Regenerate the api file. * Fix bunch of type errors caused by regenerating the api file. --------- Co-authored-by: Ivan Vasilov --- .../BasicAuthSettingsForm.tsx | 67 +++++- .../Organization/NewOrg/NewOrgForm.tsx | 7 +- .../TeamSettings/MemberActions.tsx | 2 +- apps/studio/data/api.d.ts | 196 +++++++++++++++--- .../organization-create-mutation.ts | 2 +- .../organization-members-query.ts | 6 +- 6 files changed, 241 insertions(+), 39 deletions(-) diff --git a/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx index 84aa2e5e7d0..d6958104d99 100644 --- a/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx @@ -1,4 +1,6 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { useParams } from 'common' import { observer } from 'mobx-react-lite' import { useEffect, useState } from 'react' import { @@ -17,7 +19,6 @@ import { } from 'ui' import { boolean, number, object, string } from 'yup' -import { useParams } from 'common' import { FormActions, FormHeader, @@ -26,9 +27,11 @@ import { FormSectionContent, FormSectionLabel, } from 'components/ui/Forms' +import UpgradeToPro from 'components/ui/UpgradeToPro' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useCheckPermissions, useStore } from 'hooks' +import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' +import { useCheckPermissions, useSelectedOrganization, useStore } from 'hooks' const schema = object({ DISABLE_SIGNUP: boolean().required(), @@ -44,8 +47,20 @@ const schema = object({ .oneOf(['hcaptcha', 'turnstile']) .required('Captcha provider must be either hcaptcha or turnstile'), }), + SESSIONS_TIMEBOX: number().min(0, 'Must be a positive number'), + SESSIONS_INACTIVITY_TIMEOUT: number().min(0, 'Must be a positive number'), }) +function HoursOrNeverText({ value }: { value: number }) { + if (value === 0) { + return 'never' + } else if (value === 1) { + return 'hour' + } else { + return 'hours' + } +} + const BasicAuthSettingsForm = observer(() => { const { ui } = useStore() const { ref: projectRef } = useParams() @@ -62,12 +77,21 @@ const BasicAuthSettingsForm = observer(() => { const [hidden, setHidden] = useState(true) const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') + const organization = useSelectedOrganization() + const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery({ + orgSlug: organization!.slug, + }) + + const isProPlanAndUp = isSuccessSubscription && subscription?.plan?.id !== 'free' + const INITIAL_VALUES = { DISABLE_SIGNUP: !authConfig?.DISABLE_SIGNUP, SITE_URL: authConfig?.SITE_URL, SECURITY_CAPTCHA_ENABLED: authConfig?.SECURITY_CAPTCHA_ENABLED || false, SECURITY_CAPTCHA_SECRET: authConfig?.SECURITY_CAPTCHA_SECRET || '', SECURITY_CAPTCHA_PROVIDER: authConfig?.SECURITY_CAPTCHA_PROVIDER || 'hcaptcha', + SESSIONS_TIMEBOX: authConfig?.SESSIONS_TIMEBOX || 0, + SESSIONS_INACTIVITY_TIMEOUT: authConfig?.SESSIONS_INACTIVITY_TIMEOUT || 0, } const onSubmit = (values: any, { resetForm }: any) => { @@ -152,6 +176,45 @@ const BasicAuthSettingsForm = observer(() => {
+ User Sessions}> + + {isProPlanAndUp ? ( + <> + ) : ( + + )} + + + + } + disabled={!canUpdateConfig || !isProPlanAndUp} + /> + + + + } + disabled={!canUpdateConfig || !isProPlanAndUp} + /> + + +
Bot and Abuse Protection}> { await createOrganization({ name: orgName, kind: orgKind, - tier: 'tier_' + dbTier.toLowerCase(), + tier: ('tier_' + dbTier.toLowerCase()) as + | 'tier_payg' + | 'tier_pro' + | 'tier_free' + | 'tier_team' + | 'tier_enterprise', ...(orgKind == 'COMPANY' ? { size: orgSize } : {}), payment_method: paymentMethodId, }) diff --git a/apps/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx b/apps/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx index cc87d1e3087..a244632ff1b 100644 --- a/apps/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx +++ b/apps/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx @@ -102,7 +102,7 @@ const MemberActions = ({ member, roles }: MemberActionsProps) => { await asyncDeleteMemberInvite({ slug, invitedId, invalidateDetail: false }) createOrganizationMemberInvite({ slug, - invitedEmail: member.primary_email, + invitedEmail: member.primary_email!, ownerId: invitedId, roleId: roleId, }) diff --git a/apps/studio/data/api.d.ts b/apps/studio/data/api.d.ts index ca72c8adcfc..0a4a682708e 100644 --- a/apps/studio/data/api.d.ts +++ b/apps/studio/data/api.d.ts @@ -13,10 +13,6 @@ type OneOf = T extends [infer Only] : never export interface paths { - '/platform/login': { - /** Redirects to dashboard homepage */ - get: operations['LoginController_redirectToDashboardHomepage'] - } '/platform/notifications': { /** Get notifications */ get: operations['NotificationsController_getNotificationsV2'] @@ -547,7 +543,15 @@ export interface paths { } '/platform/projects/{ref}/analytics/endpoints/functions.inv-stats': { /** Gets a project's function invocation statistics */ - get: operations['FunctionLogsController_getStatus'] + get: operations['FunctionInvocationLogsController_getStatus'] + } + '/platform/projects/{ref}/analytics/endpoints/functions.req-stats': { + /** Gets a project's function request statistics */ + get: operations['FunctionRequestLogsController_getStatus'] + } + '/platform/projects/{ref}/analytics/endpoints/functions.resource-usage': { + /** Gets a project's function resource usage */ + get: operations['FunctionResourceLogsController_getStatus'] } '/platform/projects/{ref}/analytics/endpoints/logs.all': { /** Gets project's logs */ @@ -1269,7 +1273,15 @@ export interface paths { } '/v0/projects/{ref}/analytics/endpoints/functions.inv-stats': { /** Gets a project's function invocation statistics */ - get: operations['FunctionLogsController_getStatus'] + get: operations['FunctionInvocationLogsController_getStatus'] + } + '/v0/projects/{ref}/analytics/endpoints/functions.req-stats': { + /** Gets a project's function request statistics */ + get: operations['FunctionRequestLogsController_getStatus'] + } + '/v0/projects/{ref}/analytics/endpoints/functions.resource-usage': { + /** Gets a project's function resource usage */ + get: operations['FunctionResourceLogsController_getStatus'] } '/v0/projects/{ref}/analytics/endpoints/logs.all': { /** Gets project's logs */ @@ -1691,15 +1703,28 @@ export interface components { ids: string[] } NotificationResponseV2: { + /** @enum {string} */ + type: + | 'project.tier-limit-exceeded' + | 'postgresql.upgrade-available' + | 'postgresql.upgrade-completed' + | 'project.update-completed' + | 'project.informational' + /** @enum {string} */ + status: 'new' | 'seen' | 'archived' + /** @enum {string} */ + priority: 'Critical' | 'Warning' | 'Info' id: string inserted_at: string - type: Record - status: Record - priority: Record name: string data: Record meta: Record } + UpdateNotificationBodyV2: { + id: string + /** @enum {string} */ + status: 'new' | 'seen' | 'archived' + } ResetPasswordBody: { email: string redirectTo?: string @@ -1738,6 +1763,8 @@ export interface components { redirectTo?: string } ProjectResourceWarningsResponse: { + /** @enum {string|null} */ + need_pitr: 'critical' | 'warning' | null /** @enum {string|null} */ disk_io_exhaustion: 'critical' | 'warning' | null /** @enum {string|null} */ @@ -1764,6 +1791,7 @@ export interface components { SMTP_MAX_FREQUENCY: number SMTP_SENDER_NAME?: string MAILER_AUTOCONFIRM: boolean + MAILER_ALLOW_UNVERIFIED_EMAIL_SIGN_INS: boolean MAILER_SUBJECTS_INVITE: string MAILER_SUBJECTS_CONFIRMATION: string MAILER_SUBJECTS_RECOVERY: string @@ -1782,6 +1810,8 @@ export interface components { SECURITY_CAPTCHA_ENABLED: boolean SECURITY_CAPTCHA_PROVIDER: string SECURITY_CAPTCHA_SECRET: string + SESSIONS_TIMEBOX?: number + SESSIONS_INACTIVITY_TIMEOUT?: number RATE_LIMIT_EMAIL_SENT: number RATE_LIMIT_SMS_SENT: number RATE_LIMIT_VERIFY?: number @@ -1893,6 +1923,7 @@ export interface components { SMTP_PASS_ENCRYPTED?: string | null SMTP_MAX_FREQUENCY?: number SMTP_SENDER_NAME?: string + MAILER_ALLOW_UNVERIFIED_EMAIL_SIGN_INS?: boolean MAILER_AUTOCONFIRM?: boolean MAILER_SUBJECTS_INVITE?: string MAILER_SUBJECTS_CONFIRMATION?: string @@ -1912,6 +1943,8 @@ export interface components { SECURITY_CAPTCHA_ENABLED?: boolean SECURITY_CAPTCHA_PROVIDER?: string SECURITY_CAPTCHA_SECRET?: string + SESSIONS_TIMEBOX?: number | null + SESSIONS_INACTIVITY_TIMEOUT?: number | null RATE_LIMIT_EMAIL_SENT?: number RATE_LIMIT_SMS_SENT?: number RATE_LIMIT_VERIFY?: number @@ -2025,6 +2058,7 @@ export interface components { SMTP_MAX_FREQUENCY: number SMTP_SENDER_NAME?: string MAILER_AUTOCONFIRM: boolean + MAILER_ALLOW_UNVERIFIED_EMAIL_SIGN_INS: boolean MAILER_SUBJECTS_INVITE: string MAILER_SUBJECTS_CONFIRMATION: string MAILER_SUBJECTS_RECOVERY: string @@ -2043,6 +2077,8 @@ export interface components { SECURITY_CAPTCHA_ENABLED: boolean SECURITY_CAPTCHA_PROVIDER: string SECURITY_CAPTCHA_SECRET: string + SESSIONS_TIMEBOX?: number + SESSIONS_INACTIVITY_TIMEOUT?: number RATE_LIMIT_EMAIL_SENT: number RATE_LIMIT_SMS_SENT: number RATE_LIMIT_VERIFY?: number @@ -2190,7 +2226,6 @@ export interface components { inserted_at: string } BackupsResponse: { - tierId: string tierKey: string region: string walg_enabled: boolean @@ -2234,14 +2269,25 @@ export interface components { migrated_at: string | null } OrganizationResponse: { - id: string + id: number + slug: string name: string + billing_email: string + is_owner: boolean + stripe_customer_id: string + subscription_id?: string + opt_in_tags: string[] } GetOrganizationByFlyOrganizationIdResponse: { slug: string } CreateOrganizationBody: { name: string + kind?: string + size?: string + /** @enum {string} */ + tier: 'tier_payg' | 'tier_pro' | 'tier_free' | 'tier_team' | 'tier_enterprise' + payment_method?: string } UpdateOrganizationBody: { name: string @@ -2463,9 +2509,10 @@ export interface components { } Member: { gotrue_id: string - primary_email: string + primary_email: string | null role_ids: number[] username: string + mfa_enabled: boolean } UpdateMemberBody: { role_id: number @@ -3307,7 +3354,6 @@ export interface components { region: string status: string subscription_id: string - is_readonly_mode_enabled?: boolean is_branch_enabled: boolean preview_branch_refs: string[] disk_volume_size_gb?: number @@ -3368,7 +3414,6 @@ export interface components { region: string status: string subscription_id: string - is_readonly_mode_enabled?: boolean is_branch_enabled: boolean preview_branch_refs: string[] disk_volume_size_gb?: number @@ -4147,6 +4192,11 @@ export interface components { branch: string label?: string } + CreateCliLoginSessionBody: { + session_id: string + public_key: string + token_name?: string + } FunctionResponse: { id: string slug: string @@ -4180,14 +4230,13 @@ export interface components { | 'ACTIVE_UNHEALTHY' | 'COMING_UP' | 'GOING_DOWN' - | 'INACTIVE' | 'INIT_FAILED' | 'REMOVED' | 'RESTORING' | 'UNKNOWN' | 'UPGRADING' - | 'PAUSING' reportingToken: string + databaseIdentifier: string } EventBody: { reportingToken: string @@ -4553,11 +4602,19 @@ export interface components { entrypoint_path?: string import_map_path?: string } + OrganizationResponseV1: { + id: string + name: string + } + CreateOrganizationBodyV1: { + name: string + } V1OrganizationMemberResponse: { user_id: string user_name: string email?: string role_name: string + mfa_enabled: boolean } OAuthTokenBody: { /** @enum {string} */ @@ -4745,14 +4802,6 @@ export type $defs = Record export type external = Record export interface operations { - /** Redirects to dashboard homepage */ - LoginController_redirectToDashboardHomepage: { - responses: { - 200: { - content: never - } - } - } /** Get notifications */ NotificationsController_getNotificationsV2: { parameters: { @@ -4797,7 +4846,7 @@ export interface operations { NotificationsController_updateNotificationsV2: { requestBody: { content: { - 'application/json': string[] + 'application/json': components['schemas']['UpdateNotificationBodyV2'][] } } responses: { @@ -5451,7 +5500,7 @@ export interface operations { responses: { 200: { content: { - 'application/json': components['schemas']['OrganizationResponse'][] + 'application/json': components['schemas']['OrganizationResponseV1'][] } } /** @description Unexpected error listing organizations */ @@ -8666,7 +8715,7 @@ export interface operations { } } /** Gets a project's function invocation statistics */ - FunctionLogsController_getStatus: { + FunctionInvocationLogsController_getStatus: { parameters: { query: { interval: '5min' | '15min' | '1hr' | '1day' | '7day' @@ -8692,6 +8741,60 @@ export interface operations { } } } + /** Gets a project's function request statistics */ + FunctionRequestLogsController_getStatus: { + parameters: { + query: { + interval: '5min' | '15min' | '1hr' | '1day' | '7day' + function_id: string + } + path: { + /** @description Project ref */ + ref: string + } + } + responses: { + 200: { + content: { + 'application/json': components['schemas']['AnalyticsResponse'] + } + } + 403: { + content: never + } + /** @description Failed to get project's function request statistics */ + 500: { + content: never + } + } + } + /** Gets a project's function resource usage */ + FunctionResourceLogsController_getStatus: { + parameters: { + query: { + interval: '5min' | '15min' | '1hr' | '1day' | '7day' + function_id: string + } + path: { + /** @description Project ref */ + ref: string + } + } + responses: { + 200: { + content: { + 'application/json': components['schemas']['AnalyticsResponse'] + } + } + 403: { + content: never + } + /** @description Failed to get project's function resource usage */ + 500: { + content: never + } + } + } /** Gets project's logs */ LogsController_getApiPaths: { parameters: { @@ -10124,6 +10227,40 @@ export interface operations { } } } + /** Create CLI login session */ + CliLoginController_createCliLoginSession: { + requestBody: { + content: { + 'application/json': components['schemas']['CreateCliLoginSessionBody'] + } + } + responses: { + 201: { + content: never + } + /** @description Failed to create CLI login session */ + 500: { + content: never + } + } + } + /** Retrieve CLI login session */ + CliLoginController_getCliLoginSession: { + parameters: { + path: { + session_id: string + } + } + responses: { + 200: { + content: never + } + /** @description Failed to retrieve CLI login session */ + 500: { + content: never + } + } + } /** Gets GoTrue template */ AuthTemplateController_getTemplate: { parameters: { @@ -10624,6 +10761,7 @@ export interface operations { parameters: { header: { 'x-github-delivery': string + 'x-github-event': string 'x-hub-signature-256': string } } @@ -11954,13 +12092,13 @@ export interface operations { OrganizationsController_createOrganization: { requestBody: { content: { - 'application/json': components['schemas']['CreateOrganizationBody'] + 'application/json': components['schemas']['CreateOrganizationBodyV1'] } } responses: { 201: { content: { - 'application/json': components['schemas']['OrganizationResponse'] + 'application/json': components['schemas']['OrganizationResponseV1'] } } /** @description Unexpected error creating an organization */ diff --git a/apps/studio/data/organizations/organization-create-mutation.ts b/apps/studio/data/organizations/organization-create-mutation.ts index bbb236e7596..40966f240ee 100644 --- a/apps/studio/data/organizations/organization-create-mutation.ts +++ b/apps/studio/data/organizations/organization-create-mutation.ts @@ -10,7 +10,7 @@ export type OrganizationCreateVariables = { name: string kind?: string size?: string - tier?: string + tier: 'tier_payg' | 'tier_pro' | 'tier_free' | 'tier_team' | 'tier_enterprise' payment_method?: string } diff --git a/apps/studio/data/organizations/organization-members-query.ts b/apps/studio/data/organizations/organization-members-query.ts index fbed13d829f..bb7d124e428 100644 --- a/apps/studio/data/organizations/organization-members-query.ts +++ b/apps/studio/data/organizations/organization-members-query.ts @@ -1,9 +1,9 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' +import { components } from 'data/api' import { get } from 'data/fetchers' import { ResponseError } from 'types' import { organizationKeys } from './keys' -import { components } from 'data/api' export type OrganizationMembersVariables = { slug?: string @@ -13,10 +13,6 @@ type Member = components['schemas']['Member'] export interface OrganizationMember extends Member { invited_at?: string invited_id?: number - // [Joshen] Proactively adding this to support showing if member has MFA enabled - // can be removed once infra API changes from the following PR is in - // https://github.com/supabase/infrastructure/pull/15725 - mfa_enabled?: boolean } export async function getOrganizationMembers(