Skip to content

Commit 4623a83

Browse files
authored
Merge pull request #627 from tapdata/TAP-12033
feat: TAP-12033 validate saml config and redirect login page when ena…
2 parents 7926c1a + 6729a30 commit 4623a83

10 files changed

Lines changed: 243 additions & 18 deletions

File tree

.github/workflows/ci.yml

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ jobs:
1515
outputs:
1616
i18n_files: ${{ steps.changed.outputs.i18n_files }}
1717
has_i18n_files: ${{ steps.changed.outputs.has_i18n_files }}
18+
format_files: ${{ steps.changed.outputs.format_files }}
19+
has_format_files: ${{ steps.changed.outputs.has_format_files }}
1820
steps:
1921
- uses: actions/checkout@v4
2022
with:
@@ -35,6 +37,38 @@ jobs:
3537
echo "i18n_files=" >> $GITHUB_OUTPUT
3638
fi
3739
40+
FORMAT_FILES=$(echo "$FILES" | grep -E '\.(vue|js|jsx|cjs|mjs|ts|tsx|cts|mts|json|json5|css|scss|less|html|md|mdx|yaml|yml)$' || true)
41+
if [ -n "$FORMAT_FILES" ]; then
42+
echo "has_format_files=true" >> $GITHUB_OUTPUT
43+
echo "format_files=$(echo "$FORMAT_FILES" | tr '\n' ' ')" >> $GITHUB_OUTPUT
44+
else
45+
echo "has_format_files=false" >> $GITHUB_OUTPUT
46+
echo "format_files=" >> $GITHUB_OUTPUT
47+
fi
48+
49+
check-format:
50+
name: Check formatting
51+
runs-on: ubuntu-latest
52+
needs: changed-files
53+
if: needs.changed-files.outputs.has_format_files == 'true'
54+
steps:
55+
- uses: actions/checkout@v4
56+
57+
- uses: pnpm/action-setup@v4
58+
with:
59+
version: 10.32.1
60+
61+
- uses: actions/setup-node@v4
62+
with:
63+
node-version: 20
64+
cache: pnpm
65+
66+
- name: Install dependencies
67+
run: pnpm install --frozen-lockfile
68+
69+
- name: Check formatting
70+
run: pnpm format:check -- ${{ needs.changed-files.outputs.format_files }}
71+
3872
check-i18n:
3973
name: Check i18n
4074
runs-on: ubuntu-latest
@@ -45,4 +79,3 @@ jobs:
4579

4680
- name: Check for hardcoded Chinese text
4781
run: node scripts/check-i18n.js ${{ needs.changed-files.outputs.i18n_files }}
48-

.lintstagedrc.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export default {
22
'*.{vue,js,ts,tsx,jsx}': (filenames) => {
33
const files = filenames.map((f) => `'${f}'`).join(' ')
44
return [
5+
`prettier --write ${files}`,
56
`bash -c "eslint --fix --no-warn-ignored --quiet ${files} || true"`,
67
`node scripts/check-i18n.js ${files}`,
78
]

apps/daas/src/i18n/langs/en.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -888,8 +888,7 @@ export default {
888888
setting_IdP_Initiated_Enabled: 'Allow IdP-Initiated SSO',
889889
setting_JIT_Provisioning_Enabled: 'Enable JIT Provisioning',
890890
setting_Login_Redirect_URL: 'Post-Login Redirect URL',
891-
setting_Saml_Login_Enable_doc:
892-
'Enable SAML 2.0 single sign-on login',
891+
setting_Saml_Login_Enable_doc: 'Enable SAML 2.0 single sign-on login',
893892
setting_Saml_Sp_Entity_Id_doc:
894893
'Service Provider unique identifier. Recommended to use the ACS base URL, e.g. https://tapdata.example.com/api/sso/saml',
895894
setting_Saml_Sp_Acs_Url_doc:
@@ -933,6 +932,13 @@ export default {
933932
setting_Saml_Login_Redirect_Url_doc:
934933
'Post-login redirect URL after a successful SSO login',
935934
setting_saml_generate_keypair: 'Generate Key Pair',
935+
setting_saml_test_config: 'Test Configuration',
936+
setting_saml_static_check: 'Static Configuration Check',
937+
setting_saml_test_success: 'SAML configuration test passed',
938+
setting_saml_test_failed:
939+
'SAML configuration test failed. See the details below.',
940+
setting_saml_test_no_result:
941+
'No SAML test result was returned. Please try again.',
936942
setting_saml_export_sp_metadata: 'Export SP Metadata',
937943
setting_saml_import_idp_metadata: 'Import IdP Metadata',
938944
setting_saml_import_idp_metadata_label: 'Upload the IdP metadata XML file',

apps/daas/src/i18n/langs/zh-CN.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,8 @@ export default {
762762
setting_Switch_to_batch_insert_mode_interval__s__in_cdc_:
763763
'切换到cdc中的批量插入模式间隔。',
764764
setting_share_cdc: '共享增量',
765-
setting_global_share_cdc_enable_doc: '默认开启时,使用外存列表中指定的默认外存,并且作为默认设置应用于后续新建的连接和任务。(开启后才能在数据追溯中查询变更日志)',
765+
setting_global_share_cdc_enable_doc:
766+
'默认开启时,使用外存列表中指定的默认外存,并且作为默认设置应用于后续新建的连接和任务。(开启后才能在数据追溯中查询变更日志)',
766767
setting_global_share_cdc_enable: '默认全局开启共享增量',
767768
setting_share_cdc_persistence_mode: '共享增量存储模式',
768769
setting_share_cdc_persistence_memory_size: '共享增量内存缓存行数',
@@ -780,7 +781,8 @@ export default {
780781
setting_share_cdc_persistence_rocksdb_path_doc:
781782
'该项配置只有模式选择RocksDB时生效,RocksDB存储的本地路径',
782783
setting_task_start_transform_wait_seconds: '任务启动推演等待时长(秒)',
783-
setting_task_start_transform_wait_seconds_doc: '任务点击启动后会进行推演或试运行,当超出设置的等待时长后,此次启动会失败,需要再次点击启动,或者调大推演等待时长即可,默认等待时长为60s',
784+
setting_task_start_transform_wait_seconds_doc:
785+
'任务点击启动后会进行推演或试运行,当超出设置的等待时长后,此次启动会失败,需要再次点击启动,或者调大推演等待时长即可,默认等待时长为60s',
784786
setting_task_log_file_save_time: '任务日志留存时长(天)',
785787
setting_task_log_file_save_size: '任务日志留存大小(MB)',
786788
setting_task_log_file_save_count: '任务日志保留份数',
@@ -794,7 +796,8 @@ export default {
794796
setting_Login_Brief_Tips: '登录简要提示',
795797
setting_Login_Brief_Tips_doc: '开启后,登录提示将简化',
796798
setting_Access_Token_Idle_Timeout_Minutes: '会话空闲超时时间(单位:分钟)',
797-
setting_Access_Token_Idle_Timeout_Minutes_doc: '当会话不活跃时间超过设置的值时,会话失效需重新登录',
799+
setting_Access_Token_Idle_Timeout_Minutes_doc:
800+
'当会话不活跃时间超过设置的值时,会话失效需重新登录',
798801
setting_LDAP: 'LDAP登录设置',
799802
setting_Ldap_Login_Enable: '使用LDAP登录',
800803
setting_Ldap_Server_Host: 'LDAP服务器地址',
@@ -846,7 +849,8 @@ export default {
846849
'服务提供方私钥(静态加密存储,只写),与 SP 证书通过「生成密钥对」成对生成',
847850
setting_Saml_Sp_Certificate_doc:
848851
'SP 证书(公钥,PEM),会被打包进导出的 SP 元数据。请通过「生成密钥对」生成',
849-
setting_Saml_Idp_Entity_Id_doc: '身份提供方(IdP)实体 ID,由「导入 IdP 元数据」自动填充',
852+
setting_Saml_Idp_Entity_Id_doc:
853+
'身份提供方(IdP)实体 ID,由「导入 IdP 元数据」自动填充',
850854
setting_Saml_Idp_Sso_Url_doc:
851855
'身份提供方 SSO(登录)地址。ADFS 示例:https://adfs.example.com/adfs/ls/',
852856
setting_Saml_Idp_Slo_Url_doc:
@@ -877,6 +881,11 @@ export default {
877881
'即时(JIT)预配:首次 SSO 登录时自动创建用户(默认关闭)',
878882
setting_Saml_Login_Redirect_Url_doc: 'SSO 登录成功后的跳转地址',
879883
setting_saml_generate_keypair: '生成密钥对',
884+
setting_saml_test_config: '测试配置',
885+
setting_saml_static_check: '配置静态检查',
886+
setting_saml_test_success: 'SAML 配置测试通过',
887+
setting_saml_test_failed: 'SAML 配置测试失败,请查看详细提示',
888+
setting_saml_test_no_result: '未获取到 SAML 测试结果,请稍后重试',
880889
setting_saml_export_sp_metadata: '导出 SP 元数据',
881890
setting_saml_import_idp_metadata: '导入 IdP 元数据',
882891
setting_saml_import_idp_metadata_label: '上传 IdP 元数据 XML 文件',

apps/daas/src/i18n/langs/zh-TW.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,8 @@ export default {
760760
'切換到cdc中的批量插入模式間隔。 ',
761761
setting_share_cdc: '共享增量',
762762
setting_global_share_cdc_enable: '默認全域開啟共亯增量',
763-
setting_global_share_cdc_enable_doc: '默認開啟時,使用外存清單中指定的默認外存,並且作為默認設置應用於後續新建的連接和任務。(開啟後才能在數據追溯中査詢變更日誌)',
763+
setting_global_share_cdc_enable_doc:
764+
'默認開啟時,使用外存清單中指定的默認外存,並且作為默認設置應用於後續新建的連接和任務。(開啟後才能在數據追溯中査詢變更日誌)',
764765
setting_share_cdc_persistence_mode: '共享增量存儲模式',
765766
setting_share_cdc_persistence_memory_size: '共享增量內存緩存行數',
766767
setting_share_cdc_persistence_memory_size_doc:
@@ -777,7 +778,8 @@ export default {
777778
setting_share_cdc_persistence_rocksdb_path_doc:
778779
'該項配置只有模式選擇RocksDB時生效,RocksDB存儲的本地路徑',
779780
setting_task_start_transform_wait_seconds: '任務啓動推演等待時長(秒)',
780-
setting_task_start_transform_wait_seconds_doc: '任務點擊啓動後會進行推演或試運行,當超出設置的等待時長後,此次啓動會失敗,需要再次點擊啓動,或者調大推演等待時長即可,默認等待時長爲60s',
781+
setting_task_start_transform_wait_seconds_doc:
782+
'任務點擊啓動後會進行推演或試運行,當超出設置的等待時長後,此次啓動會失敗,需要再次點擊啓動,或者調大推演等待時長即可,默認等待時長爲60s',
781783
setting_task_log_file_save_time: '任務日誌留存時長(天)',
782784
setting_task_log_file_save_size: '任務日誌留存大小(MB)',
783785
setting_task_log_file_save_count: '任務日誌留存份數',
@@ -791,7 +793,8 @@ export default {
791793
setting_Login_Brief_Tips: '登錄簡要提示',
792794
setting_Login_Brief_Tips_doc: '開啟後,登錄提示將簡化',
793795
setting_Access_Token_Idle_Timeout_Minutes: '會話空閑超時時間(單位:分鍾)',
794-
setting_Access_Token_Idle_Timeout_Minutes_doc: '當會話不活躍時間超過設置的值時,會話失效需重新登錄',
796+
setting_Access_Token_Idle_Timeout_Minutes_doc:
797+
'當會話不活躍時間超過設置的值時,會話失效需重新登錄',
795798
setting_LDAP: 'LDAP登入設置',
796799
setting_Ldap_Login_Enable: '使用LDAP登入',
797800
setting_Ldap_Server_Host: 'LDAP伺服器地址',
@@ -842,7 +845,8 @@ export default {
842845
'服務提供方私鑰(靜態加密存儲,只寫),與 SP 憑證透過「產生金鑰對」成對產生',
843846
setting_Saml_Sp_Certificate_doc:
844847
'SP 憑證(公鑰,PEM),會被打包進匯出的 SP 中繼資料。請透過「產生金鑰對」產生',
845-
setting_Saml_Idp_Entity_Id_doc: '身份提供方(IdP)實體 ID,由「匯入 IdP 中繼資料」自動填充',
848+
setting_Saml_Idp_Entity_Id_doc:
849+
'身份提供方(IdP)實體 ID,由「匯入 IdP 中繼資料」自動填充',
846850
setting_Saml_Idp_Sso_Url_doc:
847851
'身份提供方 SSO(登入)地址。ADFS 範例:https://adfs.example.com/adfs/ls/',
848852
setting_Saml_Idp_Slo_Url_doc:
@@ -873,6 +877,11 @@ export default {
873877
'即時(JIT)佈建:首次 SSO 登入時自動創建用戶(預設關閉)',
874878
setting_Saml_Login_Redirect_Url_doc: 'SSO 登入成功後的跳轉地址',
875879
setting_saml_generate_keypair: '產生金鑰對',
880+
setting_saml_test_config: '測試設定',
881+
setting_saml_static_check: '設定靜態檢查',
882+
setting_saml_test_success: 'SAML 設定測試通過',
883+
setting_saml_test_failed: 'SAML 設定測試失敗,請查看詳細提示',
884+
setting_saml_test_no_result: '未取得 SAML 測試結果,請稍後重試',
876885
setting_saml_export_sp_metadata: '匯出 SP 中繼資料',
877886
setting_saml_import_idp_metadata: '匯入 IdP 中繼資料',
878887
setting_saml_import_idp_metadata_label: '上傳 IdP 中繼資料 XML 檔案',

apps/daas/src/router/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,22 @@ router.beforeEach(async (to, from, next) => {
7171
'registyResult',
7272
].includes(to.name as string)
7373
) {
74-
next()
74+
if (to.name === 'login' && to.query?.sso === '1') {
75+
sessionStorage.setItem('samlManualLogin', '1')
76+
next({ name: 'login' })
77+
} else {
78+
next()
79+
}
7580
} else {
7681
sessionStorage.setItem('lastLocationHref', location.href)
82+
const ssoFromRoute = to.query?.sso === '1'
83+
const ssoFromUrl =
84+
new URLSearchParams(window.location.search).get('sso') === '1'
85+
if (ssoFromRoute || ssoFromUrl) {
86+
// Keep the control flag out of the visible login URL. The login page
87+
// consumes this one-time flag and stays available for manual SSO.
88+
sessionStorage.setItem('samlManualLogin', '1')
89+
}
7790
next('/login')
7891
}
7992
})

apps/daas/src/views/login/Login.vue

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
<script>
22
import { fetchSettings } from '@tap/api/core/settings'
3-
import {
4-
checkSamlLoginEnable,
5-
getSamlLoginUrl,
6-
} from '@tap/api/src/core/sso'
3+
import { checkSamlLoginEnable, getSamlLoginUrl } from '@tap/api/src/core/sso'
74
import { fetchTimestamp } from '@tap/api/src/core/timestamp'
85
import {
96
checkLdapLoginEnable,
@@ -33,12 +30,13 @@ export default {
3330
errorMessage: '',
3431
adEnable: false,
3532
samlEnable: false,
33+
samlChecking: true,
34+
samlRedirecting: false,
3635
}
3736
},
3837
created() {
3938
useDark()
4039
this.loadAdEnable()
41-
this.loadSamlEnable()
4240
if (this.$route.query) {
4341
this.form.email = this.$route.query.email
4442
const ssoError = this.$route.query.sso_error
@@ -47,6 +45,12 @@ export default {
4745
}
4846
}
4947
},
48+
mounted() {
49+
// Wait until the login route has finished mounting before starting the
50+
// browser redirect. This keeps the automatic flow aligned with the
51+
// existing button flow and avoids navigating during router resolution.
52+
this.loadSamlEnable()
53+
},
5054
methods: {
5155
getSsoErrorMessage(code) {
5256
const messages = {
@@ -64,10 +68,38 @@ export default {
6468
async loadSamlEnable() {
6569
try {
6670
this.samlEnable = await checkSamlLoginEnable()
71+
if (this.samlEnable && this.shouldRedirectToSaml()) {
72+
this.samlRedirecting = true
73+
this.loginWithSaml()
74+
return
75+
}
6776
} catch {
6877
this.samlEnable = false
78+
} finally {
79+
if (!this.samlRedirecting) {
80+
this.samlChecking = false
81+
}
6982
}
7083
},
84+
shouldRedirectToSaml() {
85+
const routeQuery = this.$route.query || {}
86+
const ssoError = routeQuery.sso_error
87+
88+
// sso=1 is used to bring an admin to the login page. It must not start
89+
// an IdP redirect automatically; the SSO button remains available there.
90+
if (sessionStorage.getItem('samlManualLogin') === '1') {
91+
sessionStorage.removeItem('samlManualLogin')
92+
return false
93+
}
94+
95+
// Keep the login page visible after an SSO failure so the user can read
96+
// the error and use the original password login flow if needed.
97+
if (ssoError) {
98+
return false
99+
}
100+
101+
return true
102+
},
71103
loginWithSaml() {
72104
// Full browser navigation so the IdP redirect chain runs in the tab.
73105
window.location.href = getSamlLoginUrl()
@@ -160,7 +192,15 @@ export default {
160192
<template>
161193
<LoginPage>
162194
<template #main>
163-
<section class="page-sign-in">
195+
<section
196+
v-if="samlChecking || samlRedirecting"
197+
class="page-sign-in saml-auto-login"
198+
>
199+
<div class="saml-auto-login-message">
200+
{{ $t('app_signIn_ssoProcessing') }}
201+
</div>
202+
</section>
203+
<section v-else class="page-sign-in">
164204
<div class="sign-in-panel">
165205
<div class="title">
166206
{{ $t('app_signIn_signIn') }}
@@ -325,4 +365,9 @@ export default {
325365
}
326366
}
327367
}
368+
369+
.saml-auto-login-message {
370+
color: var(--text-light);
371+
font-size: 16px;
372+
}
328373
</style>

0 commit comments

Comments
 (0)