fix(auth): extend login sessions to 30 days
将登录 Cookie 与签名会话期限延长至 30 天,并同步新建 Access 应用的默认期限。
This commit is contained in:
1 parent
645ed777b4
commit
93f2685140
5 files changed
+25
-3
No files matched your search
+3
-1
@@ -149,7 +149,9 @@ EdgeSSH-Auto-Update: true
|
||||
4. 从 Cloudflare 改 GitHub 时,**先解除入口域名原有的 Access 网关保护**,否则浏览器仍会先看到 Access。脚本发现这种情况会停止,不自动删除安全策略。仅解除登录网关,不要删除 Worker、D1 或 Secret。
|
||||
5. 反向切换时,Action 会核对或准备有效 Access 应用;只有 Secret 名称但入口配置不完整会明确失败。切换会递增会话代次,旧 provider 的 Cookie 即使切回原方式也不会复活。
|
||||
|
||||
GitHub 会话使用 Secure、HttpOnly、SameSite=Lax Cookie,有效期 8 小时;退出会先验证当前身份,再让该实例的全部管理员会话失效并清除本浏览器 Cookie,但不注销 GitHub 网站账号。Access 退出同样记录撤销时间并跳转到 Access 注销。会话签名通过 HKDF 从原加密密钥派生独立用途的密钥,无需用户再管理 SESSION_SECRET;撤销不会轮换 `ENCRYPTION_KEY`。无需新增用户表、设备后台或账号绑定流程。
|
||||
GitHub 会话使用 Secure、HttpOnly、SameSite=Lax Cookie,Cookie 与签名令牌有效期均为 30 天;OAuth 临时 state/PKCE Cookie 仍为 10 分钟。退出会先验证当前身份,再让该实例的全部管理员会话失效并清除本浏览器 Cookie,但不注销 GitHub 网站账号。Access 退出同样记录撤销时间并跳转到 Access 注销。会话签名通过 HKDF 从原加密密钥派生独立用途的密钥,无需用户再管理 SESSION_SECRET;撤销不会轮换 `ENCRYPTION_KEY`。无需新增用户表、设备后台或账号绑定流程。
|
||||
|
||||
新建 Cloudflare Access 应用的默认会话期限为 30 天(`720h`)。已有应用不会被普通部署改写,需在 Zero Trust 的 Access 应用设置中调整 Session Duration,并确认 Allow 策略的期限使用应用默认值或同为 30 天。已签发的 GitHub/Access Cookie 不会自动延长,重新登录后才采用新期限;主动退出和会话撤销仍可提前使其失效。
|
||||
|
||||
Cloudflare 模式仍可用 `ACCESS_IDP_IDS` 为新 Access 应用选择现成 IdP;这属于 Access 模式,不是原生 `AUTH_PROVIDER=github`。
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ export async function ensureAccess(
|
||||
name: `EdgeSSH ${settings.workerName}`,
|
||||
type: 'self_hosted',
|
||||
domain: hostname,
|
||||
session_duration: '24h',
|
||||
session_duration: '720h',
|
||||
allowed_idps: providerIds,
|
||||
auto_redirect_to_identity: providerIds.length === 1,
|
||||
policies: [{ ...policy, precedence: 1 }],
|
||||
|
||||
@@ -6,7 +6,8 @@ import { workspaceState } from './workspace.ts';
|
||||
|
||||
const sessionCookie = '__Host-edgessh-session';
|
||||
const flowCookie = '__Host-edgessh-oauth';
|
||||
const sessionSeconds = 8 * 60 * 60;
|
||||
// Cookie 与签名令牌共用 30 天期限,避免浏览器仍保留 Cookie 时服务端已判定过期。
|
||||
const sessionSeconds = 30 * 24 * 60 * 60;
|
||||
const flowSeconds = 10 * 60;
|
||||
const encoder = new TextEncoder();
|
||||
type GitHubConfig = Env & Required<Pick<Env, 'APP_ORIGIN' | 'GH_CLIENT_ID' | 'GH_CLIENT_SECRET' | 'GH_ADMIN_ID'>>;
|
||||
|
||||
@@ -88,6 +88,7 @@ test('bootstrap creates OTP and a single application with email policy; rerun is
|
||||
const result = await ensureAccess(api, settings, 'edgessh.example.workers.dev');
|
||||
assert.deepEqual(result, { ACCESS_TEAM_DOMAIN: 'team.cloudflareaccess.com', ACCESS_AUD: 'aud' });
|
||||
assert.deepEqual(app?.allowed_idps, ['otp']);
|
||||
assert.equal(app?.session_duration, '720h');
|
||||
assert.deepEqual(app?.policies, [{
|
||||
name: policy.name, decision: 'allow', include: policy.include, precedence: 1,
|
||||
}]);
|
||||
|
||||
@@ -126,6 +126,7 @@ test('OAuth starts with random state, S256 PKCE and no privileged scopes', async
|
||||
assert.equal(challenge, first.authorization.searchParams.get('code_challenge'));
|
||||
assert.equal(payload.state, first.authorization.searchParams.get('state'));
|
||||
assert.ok(first.response.headers.get('Set-Cookie')?.includes('Secure; HttpOnly; SameSite=Lax'));
|
||||
assert.match(first.response.headers.get('Set-Cookie')!, /Max-Age=600(?:;|$)/);
|
||||
assert.equal(first.authorization.toString().includes(baseEnv.GH_CLIENT_SECRET), false);
|
||||
});
|
||||
|
||||
@@ -159,6 +160,23 @@ test('only the numeric GitHub administrator receives a revision-bound session',
|
||||
assert.deepEqual({ sub: decodeJwt(cookie.split('=')[1]).sub, revision: decodeJwt(cookie.split('=')[1]).revision }, { sub: '123', revision: 1 });
|
||||
});
|
||||
|
||||
test('login cookie and signed session last 30 days and expire together', async (context) => {
|
||||
const now = Math.floor(Date.now() / 1000) * 1000;
|
||||
context.mock.timers.enable({ apis: ['Date'], now });
|
||||
const runtime = environment();
|
||||
const { response, cookie } = await login(runtime.env);
|
||||
const duration = 30 * 24 * 60 * 60;
|
||||
const header = response.headers.getSetCookie().find((value) => value.startsWith('__Host-edgessh-session='))!;
|
||||
assert.match(header, /Secure; HttpOnly; SameSite=Lax/);
|
||||
assert.match(header, /Max-Age=2592000(?:;|$)/);
|
||||
const payload = decodeJwt(cookie.split('=')[1]);
|
||||
assert.equal(payload.exp! - payload.iat!, duration);
|
||||
context.mock.timers.setTime(now + (duration - 1) * 1000);
|
||||
assert.equal((await currentAccount(request('/api/auth/me', cookie), runtime.env)).username, 'administrator');
|
||||
context.mock.timers.setTime(now + duration * 1000);
|
||||
await assert.rejects(currentAccount(request('/api/auth/me', cookie), runtime.env), /过期/);
|
||||
});
|
||||
|
||||
test('wrong signing key, tampered cookies and changed administrator cannot access APIs', async () => {
|
||||
const { cookie } = await login();
|
||||
for (const [token, settings] of [
|
||||
|
||||
Reference in new issue
Block a user