feat: harden auth and study workflows

- replace plaintext login and unlock requests with RSA-OAEP/AES-GCM encrypted payloads

- add login challenge replay protection, production RSA key validation, and auth tests

- wire compose to environment-driven dev/prod settings without committing local secrets

- update setup-config smoke scripts and Postman docs for encrypted login

- add visit schedule migrations/tests and update study/subject setup workflows
This commit is contained in:
Cheng Zhou
2026-05-08 22:13:12 +08:00
parent a7bbcaa5dc
commit 74feca4467
47 changed files with 2423 additions and 534 deletions
+1 -35
View File
@@ -74,12 +74,6 @@
</el-input>
</el-form-item>
<div class="remember-row">
<el-checkbox id="remember" v-model="form.remember" class="remember-checkbox">
<span class="remember-text">记住此设备</span>
</el-checkbox>
</div>
<el-button type="primary" :loading="loading" @click="onSubmit" size="large" class="login-btn">
</el-button>
@@ -100,7 +94,6 @@ import { useRouter } from "vue-router";
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
import { useAuthStore } from "../store/auth";
import { useStudyStore } from "../store/study";
import { getCachedCredential, setCachedCredential, clearCachedCredential } from "../utils/auth";
import { TEXT, requiredMessage } from "../locales";
import { consumeLogoutReason, LOGOUT_REASON_TIMEOUT } from "../session/sessionManager";
@@ -111,7 +104,6 @@ const formRef = ref<FormInstance>();
const form = reactive({
email: "",
password: "",
remember: false,
});
const rules: FormRules<typeof form> = {
@@ -129,12 +121,7 @@ onMounted(() => {
if (logoutReason === LOGOUT_REASON_TIMEOUT) {
ElMessage.warning("长时间未操作,已自动退出登录,请重新登录");
}
const cached = getCachedCredential();
if (cached) {
form.email = cached.email;
form.password = cached.password;
form.remember = true;
}
form.email = localStorage.getItem("ctms_last_login_email") || "";
});
const onSubmit = async () => {
@@ -144,11 +131,6 @@ const onSubmit = async () => {
loading.value = true;
try {
await auth.login(form.email, form.password);
if (form.remember) {
setCachedCredential(form.email, form.password);
} else {
clearCachedCredential();
}
const studyStore = useStudyStore();
if (!studyStore.currentStudy) {
if (auth.user?.role === "ADMIN") {
@@ -327,22 +309,6 @@ const onSubmit = async () => {
transition: background-color 5000s ease-in-out 0s;
}
.remember-row {
display: flex;
align-items: center;
justify-content: flex-start;
margin: -6px 0 18px;
}
.remember-checkbox :deep(.el-checkbox__label) {
padding-left: 8px;
}
.remember-text {
color: #64748b;
font-size: 13px;
}
.login-btn {
width: 100%;
height: 48px;