Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/dispatch_storybook_release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
aws-region: ap-northeast-2

- name: Deploy to s3
run: aws s3 sync apps/storybook/storybook-static/ s3://${{ secrets.STORYBOOK_S3_BUCKET }} --delete
run: aws s3 sync apps/mirinae-storybook/storybook-static/ s3://${{ secrets.STORYBOOK_S3_BUCKET }} --delete

- name: Invalidate CloudFront Cache
run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CDN_DISTRIBUTION_ID }} --paths "/*"
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "web",
"version": "2.0.0-dev377",
"version": "2.0.0-dev378",
"private": true,
"description": "Cloudforet Console Web Application",
"author": "Cloudforet",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { SpaceConnector } from '@cloudforet/core-lib/space-connector';

import type { ListResponse } from '@/api-clients/_common/schema/api-verbs/list';
import type { UserProfileConfirmEmailParameters } from '@/api-clients/identity/user-profile/schema/api-verbs/confirm-email';
import type { UserProfileConfirmMfaParameters } from '@/api-clients/identity/user-profile/schema/api-verbs/confirm-mfa';
import type { UserProfileDisableMfaParameters } from '@/api-clients/identity/user-profile/schema/api-verbs/disable-mfa';
import type { UserProfileEnableMfaParameters } from '@/api-clients/identity/user-profile/schema/api-verbs/enable-mfa';
import type { UserProfileGetWorkspacesParameters } from '@/api-clients/identity/user-profile/schema/api-verbs/get-workspaces';
import type { UserProfileResetPasswordParameters } from '@/api-clients/identity/user-profile/schema/api-verbs/reset-password';
import type { UserProfileUpdateParameters } from '@/api-clients/identity/user-profile/schema/api-verbs/update';
import type { UserProfileUpdatePasswordParameters } from '@/api-clients/identity/user-profile/schema/api-verbs/update-password';
import type { UserProfileVerifyEmailParameters } from '@/api-clients/identity/user-profile/schema/api-verbs/verify-email';
import type { WorkspaceGroupModel } from '@/api-clients/identity/workspace-group/schema/model';
import type { WorkspaceModel } from '@/api-clients/identity/workspace/schema/model';
import type { UserModel } from '@/api-clients/identity/user/schema/model';



export const useUserProfileApi = () => {
Expand All @@ -19,12 +19,11 @@ export const useUserProfileApi = () => {
updatePassword: SpaceConnector.clientV2.identity.userProfile.updatePassword<UserProfileUpdatePasswordParameters, any>,
resetPassword: SpaceConnector.clientV2.identity.userProfile.resetPassword<UserProfileResetPasswordParameters, any>,
verifyEmail: SpaceConnector.clientV2.identity.userProfile.verifyEmail<UserProfileVerifyEmailParameters, any>,
confirmEmail: SpaceConnector.clientV2.identity.userProfile.confirmEmail<UserProfileConfirmEmailParameters, any>,
enableMfa: SpaceConnector.clientV2.identity.userProfile.enableMfa<UserProfileEnableMfaParameters, any>,
confirmMfa: SpaceConnector.clientV2.identity.userProfile.confirmMfa<UserProfileConfirmMfaParameters, any>,
getWorkspaces: SpaceConnector.clientV2.identity.userProfile.getWorkspaces<UserProfileGetWorkspacesParameters, ListResponse<WorkspaceModel>>,
getWorkspaceGroups: SpaceConnector.clientV2.identity.userProfile.getWorkspaceGroups<undefined, ListResponse<WorkspaceGroupModel>>,

confirmEmail: SpaceConnector.clientV2.identity.userProfile.confirmEmail<UserProfileConfirmEmailParameters, UserModel>,
disableMfa: SpaceConnector.clientV2.identity.userProfile.disableMfa<UserProfileDisableMfaParameters, UserModel>,
enableMfa: SpaceConnector.clientV2.identity.userProfile.enableMfa<UserProfileEnableMfaParameters, UserModel>,
confirmMfa: SpaceConnector.clientV2.identity.userProfile.confirmMfa<UserProfileConfirmMfaParameters, UserModel>,
getWorkspaces: SpaceConnector.clientV2.identity.userProfile.getWorkspaces<UserProfileGetWorkspacesParameters, any>,
};

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/* eslint-disable @typescript-eslint/no-empty-interface */
export interface UserProfileDisableMfaParameters {
// No parameters
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ export const MULTI_FACTOR_AUTH_TYPE = {
OTP: 'OTP',
EMAIL: 'EMAIL',
} as const;

export const MFA_STATE = {
ENABLED: 'ENABLED',
DISABLED: 'DISABLED',
} as const;
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { MULTI_FACTOR_AUTH_TYPE } from '@/api-clients/identity/user-profile/schema/constant';
import type { MFA_STATE, MULTI_FACTOR_AUTH_TYPE } from '@/api-clients/identity/user-profile/schema/constant';

export type MultiFactorAuthType = typeof MULTI_FACTOR_AUTH_TYPE[keyof typeof MULTI_FACTOR_AUTH_TYPE];
export type MfaState = typeof MFA_STATE[keyof typeof MFA_STATE];
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Tags } from '@/api-clients/_common/schema/model';
import type { MfaState, MultiFactorAuthType } from '@/api-clients/identity/user-profile/schema/type';
import type { AuthType } from '@/api-clients/identity/user/schema/type';


Expand All @@ -12,4 +13,6 @@ export interface UserCreateParameters {
timezone?: string;
tags?: Tags;
reset_password?: boolean;
enforce_mfa_state?: MfaState;
enforce_mfa_type?: MultiFactorAuthType; // only when enforce_mfa_state is ENABLED, this field is required
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Tags } from '@/api-clients/_common/schema/model';
import type { MfaState, MultiFactorAuthType } from '@/api-clients/identity/user-profile/schema/type';


export interface UserUpdateParameters {
Expand All @@ -10,4 +11,6 @@ export interface UserUpdateParameters {
timezone?: string;
tags?: Tags;
reset_password?: boolean;
enforce_mfa_state?: MfaState;
enforce_mfa_type?: MultiFactorAuthType; // only when enforce_mfa_state is ENABLED, this field is required
}
10 changes: 6 additions & 4 deletions apps/web/src/api-clients/identity/user/schema/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface UserModel {
auth_type: AuthType; // backend
role_type: RoleType;
role_id?: string;
mfa: UserMfa;
mfa?: UserMfa;
language: string;
timezone: string;
api_key_count: number;
Expand All @@ -26,11 +26,13 @@ export interface UserModel {
}

export interface UserMfa {
state: UserMfaState,
mfa_type: MultiFactorAuthType,
options: {
state?: UserMfaState,
mfa_type?: MultiFactorAuthType,
options?: {
enforce?: boolean, // if true, mfa_type is required
email?: string,
user_secret_id?: string,
otp_qrcode_uri?: string, // response from 'enable-mfa' verb only
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Tags } from '@/api-clients/_common/schema/model';
import type { MfaState, MultiFactorAuthType } from '@/api-clients/identity/user-profile/schema/type';
import type { AuthType } from '@/api-clients/identity/user/schema/type';

export interface WorkspaceUserCreateParameters {
Expand All @@ -11,5 +12,7 @@ export interface WorkspaceUserCreateParameters {
timezone?: string;
tags?: Tags;
reset_password?: boolean;
enforce_mfa_state?: MfaState;
enforce_mfa_type?: MultiFactorAuthType;
role_id: string;
}
4 changes: 3 additions & 1 deletion apps/web/src/common/components/info-tooltip/InfoTooltip.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<script setup lang="ts">
import type { TranslateResult } from 'vue-i18n';

import { PTooltip, PI } from '@cloudforet/mirinae';
import type { TooltipPosition } from '@cloudforet/mirinae/types/data-display/tooltips/type';

interface Props {
tooltipContents: string;
tooltipContents: string|TranslateResult;
tooltipPosition?: TooltipPosition;
width?: string;
height?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import { postUserProfileDisableMfa, postEnableMfa } from '@/lib/helper/multi-fac

import { useProxyValue } from '@/common/composables/proxy-state';

const RE_SEND_CODE_COOLDOWN_TIME = 10000;

interface Props {
isDisabledModal: boolean
isReSyncModal: boolean
isSentCode: boolean
isDisabledModal?: boolean
isReSyncModal?: boolean
isSentCode?: boolean
}

const props = withDefaults(defineProps<Props>(), {
Expand All @@ -32,20 +34,38 @@ const storeState = reactive({
const state = reactive({
isCollapsed: true,
proxyIsSentCode: useProxyValue('is-sent-code', props, emit),
loading: false,
cooldown: false,
});

const startCooldown = () => {
state.cooldown = true;
setTimeout(() => {
state.cooldown = false;
}, RE_SEND_CODE_COOLDOWN_TIME);
};

const handleClickSendEmailButton = async () => {
if (props.isDisabledModal || props.isReSyncModal) {
await postUserProfileDisableMfa();
} else {
await postEnableMfa({
mfa_type: MULTI_FACTOR_AUTH_TYPE.EMAIL,
options: {
email: storeState.email,
},
});
if (state.cooldown) return;

state.loading = true;

try {
if (props.isDisabledModal || props.isReSyncModal) {
await postUserProfileDisableMfa();
} else {
await postEnableMfa({
mfa_type: MULTI_FACTOR_AUTH_TYPE.EMAIL,
options: {
email: storeState.email,
},
});
}
state.proxyIsSentCode = true;
startCooldown();
} finally {
state.loading = false;
}
state.proxyIsSentCode = true;
};
</script>

Expand All @@ -62,7 +82,7 @@ const handleClickSendEmailButton = async () => {
{{ $t('COMMON.MFA_MODAL.COLLAPSE_DESC') }}
<p-text-button class="send-code-button"
style-type="highlight"
:disabled="(props.isDisabledModal || props.isReSyncModal) ? !storeState.email : !props.isSentCode"
:disabled="((props.isDisabledModal || props.isReSyncModal) ? !storeState.email : !props.isSentCode) || state.loading || state.cooldown"
@click.prevent="handleClickSendEmailButton"
>
<span class="emphasis">{{ $t('COMMON.MFA_MODAL.SEND_NEW_CODE') }}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,22 @@ import { emailValidator } from '@/lib/helper/user-validation-helper';
import { useFormValidator } from '@/common/composables/form-validator';
import { useProxyValue } from '@/common/composables/proxy-state';

import { useMultiFactorAuthStore } from '@/services/my-page/stores/multi-factor-auth-store';

interface Props {
isSentCode?: boolean
isSentCode?: boolean;
isForm?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
isSentCode: false,
isForm: false,
});

const multiFactorAuthStore = useMultiFactorAuthStore();
const multiFactorAuthState = multiFactorAuthStore.state;
const userStore = useUserStore();

const emit = defineEmits<{(e: 'update:is-sent-code'): void }>();

const storeState = reactive({
email: computed<string|undefined>(() => userStore.state.mfa?.options?.email),
isFormModal: computed(() => multiFactorAuthState.modalType === 'FORM'),
});
const state = reactive({
loading: false,
Expand All @@ -58,7 +55,7 @@ const {
const handleClickSendCodeButton = async () => {
state.loading = true;
try {
if (storeState.isFormModal) {
if (props.isForm) {
await postEnableMfa({
mfa_type: MULTI_FACTOR_AUTH_TYPE.EMAIL,
options: {
Expand All @@ -82,9 +79,9 @@ const handleClickSendCodeButton = async () => {

<template>
<div class="email-info-wrapper"
:class="{'form-modal': storeState.isFormModal}"
:class="{'form-modal': props.isForm}"
>
<div v-if="storeState.isFormModal"
<div v-if="props.isForm"
class="email-form-wrapper"
>
<p-field-group
Expand Down
40 changes: 40 additions & 0 deletions apps/web/src/common/components/mfa/components/OTPForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script setup lang="ts">
import OTPQRInfo from '@/common/components/mfa/components/OTPQRInfo.vue';
import VerificationCodeForm from '@/common/components/mfa/components/VerificationCodeForm.vue';
import { useProxyValue } from '@/common/composables/proxy-state';

interface Props {
verificationCode: string;
verificationCodeInvalid: boolean;
invalidText: string;
}

interface Emits {
(e: 'update:verification-code', value: string): void;
(e: 'update:verification-code-invalid', value: boolean): void;
}

const props = withDefaults(defineProps<Props>(), {
verificationCode: '',
verificationCodeInvalid: false,
invalidText: '',
});

const emit = defineEmits<Emits>();

/* State */
const verificationCode = useProxyValue<string>('verificationCode', props, emit);
const verificationCodeInvalid = useProxyValue<boolean>('verificationCodeInvalid', props, emit);


</script>

<template>
<div class="o-t-p-form">
<o-t-p-q-r-info />
<verification-code-form :invalid.sync="verificationCodeInvalid"
:code-value.sync="verificationCode"
:invalid-text="props.invalidText"
/>
</div>
</template>
Loading
Loading