Browse Source

feat: 添加 Gitea OAuth 支持,更新环境变量和登录页面

as
npmrun 2 weeks ago
parent
commit
365069f711
  1. 3
      .env.example
  2. 67
      app/pages/auth/login.vue
  3. 4
      server/api/auth/oauth/[provider]/callback.get.ts
  4. 28
      server/service/oauth/oauth-manager.ts

3
.env.example

@ -8,4 +8,7 @@ BOOTSTRAP_ADMIN_USERNAME=admin
BOOTSTRAP_ADMIN_PASSWORD=123456qaz
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
GITEA_CLIENT_ID=your_gitea_client_id
GITEA_CLIENT_SECRET=your_gitea_client_secret
GITEA_URL=https://gitea.com
APP_URL=http://localhost:3399

67
app/pages/auth/login.vue

@ -7,21 +7,6 @@ const route = useRoute()
const { $toast } = useNuxtApp()
const redirect = computed(() => route.query.redirect as string || '/')
onMounted(() => {
const url = new URL(window.location.href)
if (url.searchParams.get('oauth_success') === '1') {
refresh(true)
router.push('/')
}
if (url.searchParams.get('oauth_error')) {
const error = url.searchParams.get('oauth_error')
console.error('OAuth error:', error)
url.searchParams.delete('oauth_error')
window.history.replaceState({}, document.title, url.href);
$toast.error(`OAuth 登录失败: ${error}`)
}
})
const loginForm = reactive({
username: '',
password: '',
@ -43,6 +28,10 @@ async function loginWithGithub() {
window.location.href = '/api/auth/oauth/github/authorize';
}
async function loginWithGitea() {
window.location.href = '/api/auth/oauth/gitea/authorize';
}
async function fetchCaptcha() {
captcha.loading = true
try {
@ -82,6 +71,7 @@ async function handleLogin() {
onMounted(() => {
fetchCaptcha()
const url = new URL(window.location.href)
if (url.searchParams.get('oauth_success') === '1') {
refresh(true)
@ -90,6 +80,9 @@ onMounted(() => {
if (url.searchParams.get('oauth_error')) {
const error = url.searchParams.get('oauth_error')
console.error('OAuth error:', error)
url.searchParams.delete('oauth_error')
window.history.replaceState({}, document.title, url.href)
$toast.error(`OAuth 登录失败: ${error}`)
}
})
</script>
@ -155,12 +148,22 @@ onMounted(() => {
<span class="divider-line"></span>
</div>
<button class="social-btn" @click="loginWithGithub">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
使用 GitHub 登录
</button>
<div class="social-row">
<button class="social-btn" @click="loginWithGithub">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
<span>GitHub</span>
</button>
<button class="social-btn" @click="loginWithGitea">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path d="M2.5 10.5a.5.5 0 0 1 .5.5v1.5a.5.5 0 0 1-1 0V11a.5.5 0 0 1 .5-.5Zm18.5.5v1.5a.5.5 0 0 0 1 0V11a.5.5 0 0 0-1 0ZM12 15.5a1.5 1.5 0 0 1 1.5 1.5v1a.5.5 0 0 0 1 0v-1a2.5 2.5 0 0 0-5 0v1a.5.5 0 0 0 1 0v-1A1.5 1.5 0 0 1 12 15.5Z"/>
<path d="M20.5 12A8.5 8.5 0 0 0 12 3.5 8.5 8.5 0 0 0 3.5 12v5.25c0 .69.56 1.25 1.25 1.25h2.5c.69 0 1.25-.56 1.25-1.25V13.5c0-.69-.56-1.25-1.25-1.25h-2.5c-.09 0-.17.01-.25.02v-.27a8.5 8.5 0 0 1 17 0v.27a1.2 1.2 0 0 0-.25-.02h-2.5c-.69 0-1.25.56-1.25 1.25v3.75c0 .69.56 1.25 1.25 1.25H19.25c.69 0 1.25-.56 1.25-1.25V12Z"/>
</svg>
<span>Gitea</span>
</button>
</div>
<p class="form-footer">
还没有账户<NuxtLink to="/auth/register">立即注册</NuxtLink>
@ -557,29 +560,39 @@ onMounted(() => {
letter-spacing: 1px;
}
.social-row {
display: flex;
gap: 12px;
}
.social-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
width: 100%;
padding: 16px 20px;
gap: 10px;
padding: 14px 16px;
font-family: var(--font-body);
font-size: 14px;
font-weight: 500;
color: var(--color-ink);
color: var(--color-muted);
background: var(--color-canvas);
border: 1.5px solid var(--color-hairline);
border-radius: 8px;
border-radius: 10px;
cursor: pointer;
transition: background 0.2s ease, border-color 0.2s ease;
transition: all 0.2s ease;
}
.social-btn:hover {
color: var(--color-ink);
background: var(--color-surface-soft);
border-color: var(--color-hairline-soft);
}
.social-btn:active {
transform: scale(0.97);
}
.form-footer {
text-align: center;
margin-top: 24px;

4
server/api/auth/oauth/[provider]/callback.get.ts

@ -20,9 +20,9 @@ export default defineWrappedResponseHandler(async (event) => {
setSessionCookie(event, result.sessionId);
}
return sendRedirect(event, `${FRONTEND_LOGIN_PATH}??oauth_success=1`);
return sendRedirect(event, `${FRONTEND_LOGIN_PATH}?oauth_success=1`);
} catch (error) {
const errorCode = error instanceof OAuthError ? error.code : 'OAUTH_UNKNOWN';
return sendRedirect(event, `${FRONTEND_LOGIN_PATH}??oauth_error=${errorCode}`);
return sendRedirect(event, `${FRONTEND_LOGIN_PATH}?oauth_error=${errorCode}`);
}
});

28
server/service/oauth/oauth-manager.ts

@ -46,8 +46,11 @@ export type OAuthProviderConfig = {
tokenUrl: string;
userInfoUrl: string;
scopes: string[];
responseType?: string;
redirectUri: string;
userInfoMapping: FieldMapping;
grant_type?: string;
headers?: object;
};
const providers: Record<string, OAuthProviderConfig> = {
@ -68,6 +71,25 @@ const providers: Record<string, OAuthProviderConfig> = {
avatar: 'avatar_url',
},
},
gitea: {
name: 'gitea',
icon: 'gitea', // 或者使用自定义图标
clientId: process.env.GITEA_CLIENT_ID || '',
clientSecret: process.env.GITEA_CLIENT_SECRET || '',
authorizeUrl: `${process.env.GITEA_URL}/login/oauth/authorize`,
tokenUrl: `${process.env.GITEA_URL}/login/oauth/access_token`,
userInfoUrl: `${process.env.GITEA_URL}/api/v1/user`,
scopes: ['read:user', 'user:email'],
grant_type: "authorization_code",
responseType: "code",
redirectUri: `${process.env.APP_URL}/api/auth/oauth/gitea/callback`,
userInfoMapping: {
providerUserId: 'id',
username: 'login',
email: 'email',
avatar: 'avatar_url',
},
},
};
export class OAuthManager {
@ -102,6 +124,10 @@ export class OAuthManager {
state,
});
if (provider.responseType) {
params.set('response_type', provider.responseType);
}
return `${provider.authorizeUrl}?${params.toString()}`;
}
@ -260,11 +286,13 @@ export class OAuthManager {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
...(provider.headers || {})
},
body: JSON.stringify({
client_id: provider.clientId,
client_secret: provider.clientSecret,
code,
...(provider.grant_type ? { grant_type: provider.grant_type } : {}),
redirect_uri: provider.redirectUri,
}),
});

Loading…
Cancel
Save