mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-09-05 15:47:00 +08:00
refactor: improve GitHub OAuth handling and update authentication flow
This commit is contained in:
@@ -1,17 +1,14 @@
|
|||||||
using System.Security.Claims;
|
|
||||||
using Foxel.Models;
|
using Foxel.Models;
|
||||||
using Foxel.Services.Interface;
|
using Foxel.Services.Interface;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Foxel.Models.Request.Auth;
|
using Foxel.Models.Request.Auth;
|
||||||
using Foxel.Models.Response.Auth;
|
using Foxel.Models.Response.Auth;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
|
||||||
|
|
||||||
namespace Foxel.Controllers;
|
namespace Foxel.Controllers;
|
||||||
|
|
||||||
[Route("api/auth")]
|
[Route("api/auth")]
|
||||||
public class AuthController(IUserService userService) : BaseApiController
|
public class AuthController(IUserService userService, IConfigService configService) : BaseApiController
|
||||||
{
|
{
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
public async Task<ActionResult<BaseResult<AuthResponse>>> Register([FromBody] RegisterRequest request)
|
public async Task<ActionResult<BaseResult<AuthResponse>>> Register([FromBody] RegisterRequest request)
|
||||||
@@ -101,73 +98,110 @@ public class AuthController(IUserService userService) : BaseApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("github/login")]
|
[HttpGet("github/login")]
|
||||||
public IActionResult GitHubLogin(string returnUrl = "/")
|
public IActionResult GitHubLogin()
|
||||||
{
|
{
|
||||||
try
|
string githubClientId = configService["Authentication:GitHubClientId"];
|
||||||
{
|
string githubCallback = configService["Authentication:GitHubRedirectUri"];
|
||||||
var properties = new AuthenticationProperties
|
string githubAuthorizeUrl =
|
||||||
{
|
$"https://github.com/login/oauth/authorize?client_id={Uri.EscapeDataString(githubClientId)}&redirect_uri={Uri.EscapeDataString(githubCallback)}";
|
||||||
RedirectUri = Url.Action("GitHubCallback", new { returnUrl }),
|
return Redirect(githubAuthorizeUrl);
|
||||||
Items = { { "returnUrl", returnUrl } },
|
|
||||||
// 添加超时设置
|
|
||||||
AllowRefresh = true,
|
|
||||||
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),
|
|
||||||
IsPersistent = false
|
|
||||||
};
|
|
||||||
return Challenge(properties, "GitHub");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"GitHub登录异常: {ex}");
|
|
||||||
return Redirect("/login?error=github_login_error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("github/callback")]
|
[HttpGet("github/callback")]
|
||||||
public async Task<IActionResult> GitHubCallback(string returnUrl = "/")
|
public async Task<ActionResult<BaseResult<string>>> GitHubCallback(string code)
|
||||||
{
|
{
|
||||||
try
|
if (string.IsNullOrEmpty(code))
|
||||||
{
|
{
|
||||||
var authenticateResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
return Error<string>("GitHub授权码无效");
|
||||||
if (!authenticateResult.Succeeded)
|
|
||||||
{
|
|
||||||
Console.WriteLine("GitHub认证失败: 无法获取认证结果");
|
|
||||||
return Redirect("/login?error=github_auth_failed");
|
|
||||||
}
|
|
||||||
// 获取GitHub用户信息
|
|
||||||
var githubId = authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
|
||||||
var githubLogin = authenticateResult.Principal.FindFirst("urn:github:login")?.Value;
|
|
||||||
var githubEmail = authenticateResult.Principal.FindFirst(ClaimTypes.Email)?.Value;
|
|
||||||
|
|
||||||
Console.WriteLine($"GitHub用户信息: ID={githubId}, Login={githubLogin}, Email={githubEmail}");
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(githubId) || string.IsNullOrEmpty(githubLogin))
|
|
||||||
{
|
|
||||||
return Redirect("/login?error=github_missing_info");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 登出Cookie认证会话
|
|
||||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
|
||||||
|
|
||||||
// 查找或创建用户
|
|
||||||
var (success, message, user) = await userService.FindOrCreateGitHubUserAsync(
|
|
||||||
githubId, githubLogin, githubEmail);
|
|
||||||
|
|
||||||
if (!success || user == null)
|
|
||||||
{
|
|
||||||
return Redirect($"/login?error={Uri.EscapeDataString(message)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成JWT令牌
|
|
||||||
var token = await userService.GenerateJwtTokenAsync(user);
|
|
||||||
|
|
||||||
// 重定向回前端,携带token参数
|
|
||||||
return Redirect($"{returnUrl}?token={Uri.EscapeDataString(token)}");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
string githubClientId = configService["Authentication:GitHubClientId"];
|
||||||
|
string githubClientSecret = configService["Authentication:GitHubClientSecret"];
|
||||||
|
string githubTokenUrl = "https://github.com/login/oauth/access_token";
|
||||||
|
string githubUserApiUrl = "https://api.github.com/user";
|
||||||
|
|
||||||
|
using var httpClient = new HttpClient();
|
||||||
|
httpClient.DefaultRequestHeaders.Add("User-Agent", "Foxel");
|
||||||
|
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||||
|
var tokenRequestUrl =
|
||||||
|
$"{githubTokenUrl}?client_id={Uri.EscapeDataString(githubClientId)}&client_secret={Uri.EscapeDataString(githubClientSecret)}&code={Uri.EscapeDataString(code)}";
|
||||||
|
var tokenResponse = await httpClient.PostAsync(tokenRequestUrl, null);
|
||||||
|
|
||||||
|
if (!tokenResponse.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"GitHub回调处理异常: {ex}");
|
var errorContent = await tokenResponse.Content.ReadAsStringAsync();
|
||||||
return Redirect("/login?error=github_callback_error");
|
Console.WriteLine($"获取GitHub访问令牌失败: {tokenResponse.StatusCode}, {errorContent}");
|
||||||
|
return Error<string>($"获取GitHub访问令牌失败: {errorContent}", (int)tokenResponse.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tokenResponseContent = await tokenResponse.Content.ReadAsStringAsync();
|
||||||
|
var tokenJson = System.Text.Json.JsonDocument.Parse(tokenResponseContent);
|
||||||
|
|
||||||
|
if (!tokenJson.RootElement.TryGetProperty("access_token", out var accessTokenElement) ||
|
||||||
|
accessTokenElement.GetString() == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"GitHub响应中未找到access_token: {tokenResponseContent}");
|
||||||
|
return Error<string>("获取GitHub访问令牌失败,响应中未包含令牌。");
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessToken = accessTokenElement.GetString();
|
||||||
|
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization =
|
||||||
|
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
|
||||||
|
|
||||||
|
var userResponse = await httpClient.GetAsync(githubUserApiUrl);
|
||||||
|
if (!userResponse.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var errorContent = await userResponse.Content.ReadAsStringAsync();
|
||||||
|
Console.WriteLine($"获取GitHub用户信息失败: {userResponse.StatusCode}, {errorContent}");
|
||||||
|
return Error<string>($"获取GitHub用户信息失败: {errorContent}", (int)userResponse.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
var userContent = await userResponse.Content.ReadAsStringAsync();
|
||||||
|
var userJson = System.Text.Json.JsonDocument.Parse(userContent);
|
||||||
|
|
||||||
|
string? githubUserId = null;
|
||||||
|
string? email = null;
|
||||||
|
string? name = null;
|
||||||
|
string? loginName = null;
|
||||||
|
|
||||||
|
if (userJson.RootElement.TryGetProperty("id", out var idElement))
|
||||||
|
{
|
||||||
|
githubUserId = idElement.GetInt64().ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userJson.RootElement.TryGetProperty("email", out var emailElement))
|
||||||
|
{
|
||||||
|
email = emailElement.GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userJson.RootElement.TryGetProperty("name", out var nameElement))
|
||||||
|
{
|
||||||
|
name = nameElement.GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userJson.RootElement.TryGetProperty("login", out var loginElement))
|
||||||
|
{
|
||||||
|
loginName = loginElement.GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(githubUserId))
|
||||||
|
{
|
||||||
|
return Error<string>("无法从GitHub获取用户ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var (isSuccess, message, user) =
|
||||||
|
await userService.FindOrCreateGitHubUserAsync(githubUserId, name ?? loginName, email);
|
||||||
|
|
||||||
|
if (!isSuccess || user == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"创建或查找GitHub用户失败: {message}");
|
||||||
|
return Redirect(
|
||||||
|
$"/login?error=github_user_creation_failed&message={Uri.EscapeDataString(message)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = await userService.GenerateJwtTokenAsync(user);
|
||||||
|
return Redirect($"/login?token={Uri.EscapeDataString(token)}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
EXPOSE 8081
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
FROM oven/bun:alpine AS build-frontend
|
FROM oven/bun:alpine AS build-frontend
|
||||||
@@ -31,8 +32,8 @@ RUN apt-get update && apt-get install -y nginx && rm -rf /var/lib/apt/lists/*
|
|||||||
COPY --from=build-frontend /src/View/dist /var/www/html
|
COPY --from=build-frontend /src/View/dist /var/www/html
|
||||||
COPY /View/nginx.conf /etc/nginx/nginx.conf
|
COPY /View/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|
||||||
RUN mkdir -p /var/lib/nginx/body /var/cache/nginx /var/run/nginx \
|
RUN mkdir -p /var/lib/nginx/body /var/cache/nginx /var/run/nginx /app/Uploads \
|
||||||
&& chown -R $APP_UID:$APP_UID /var/lib/nginx /var/cache/nginx /var/run/nginx /var/log/nginx /etc/nginx /var/www/html \
|
&& chown -R $APP_UID:$APP_UID /var/lib/nginx /var/cache/nginx /var/run/nginx /var/log/nginx /etc/nginx /var/www/html /app/Uploads \
|
||||||
&& mkdir -p /run \
|
&& mkdir -p /run \
|
||||||
&& chmod 777 /run
|
&& chmod 777 /run
|
||||||
|
|
||||||
|
|||||||
@@ -67,35 +67,6 @@ public static class ServiceCollectionExtensions
|
|||||||
IssuerSigningKey = new SymmetricSecurityKey(
|
IssuerSigningKey = new SymmetricSecurityKey(
|
||||||
Encoding.UTF8.GetBytes(configuration["Jwt:SecretKey"]))
|
Encoding.UTF8.GetBytes(configuration["Jwt:SecretKey"]))
|
||||||
};
|
};
|
||||||
})
|
|
||||||
.AddCookie(options =>
|
|
||||||
{
|
|
||||||
options.Cookie.HttpOnly = true;
|
|
||||||
options.Cookie.SameSite = SameSiteMode.Lax;
|
|
||||||
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
|
|
||||||
})
|
|
||||||
.AddGitHub(options =>
|
|
||||||
{
|
|
||||||
options.ClientId = configuration["Authentication:GitHubClientId"];
|
|
||||||
options.ClientSecret = configuration["Authentication:GitHubClientSecret"];
|
|
||||||
options.CallbackPath = "/api/auth/github/callback";
|
|
||||||
options.Scope.Add("user:email");
|
|
||||||
options.BackchannelHttpHandler = new HttpClientHandler
|
|
||||||
{
|
|
||||||
UseCookies = false,
|
|
||||||
AllowAutoRedirect = false,
|
|
||||||
MaxConnectionsPerServer = 100
|
|
||||||
};
|
|
||||||
options.Events = new Microsoft.AspNetCore.Authentication.OAuth.OAuthEvents
|
|
||||||
{
|
|
||||||
OnRemoteFailure = context =>
|
|
||||||
{
|
|
||||||
Console.WriteLine($"GitHub登录失败: {context.Failure}");
|
|
||||||
context.Response.Redirect("/login?error=github_remote_error");
|
|
||||||
context.HandleResponse();
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,10 +4,10 @@
|
|||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="9.3.0" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.5" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.5" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
|
||||||
|
|||||||
+9
-4
@@ -1,5 +1,6 @@
|
|||||||
using Foxel.Extensions;
|
using Foxel.Extensions;
|
||||||
using Foxel.Services.Interface;
|
using Foxel.Services.Interface;
|
||||||
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
var environment = builder.Environment;
|
var environment = builder.Environment;
|
||||||
@@ -14,26 +15,30 @@ builder.Services.AddHttpContextAccessor();
|
|||||||
builder.Services.AddApplicationAuthentication();
|
builder.Services.AddApplicationAuthentication();
|
||||||
builder.Services.AddApplicationAuthorization();
|
builder.Services.AddApplicationAuthorization();
|
||||||
builder.Services.AddApplicationCors();
|
builder.Services.AddApplicationCors();
|
||||||
|
builder.Services.Configure<ForwardedHeadersOptions>(options =>
|
||||||
|
{
|
||||||
|
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
|
||||||
|
options.KnownNetworks.Clear();
|
||||||
|
options.KnownProxies.Clear();
|
||||||
|
});
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// 初始化数据库配置
|
|
||||||
using (var scope = app.Services.CreateScope())
|
using (var scope = app.Services.CreateScope())
|
||||||
{
|
{
|
||||||
var initializer = scope.ServiceProvider.GetRequiredService<IDatabaseInitializer>();
|
var initializer = scope.ServiceProvider.GetRequiredService<IDatabaseInitializer>();
|
||||||
await initializer.InitializeAsync();
|
await initializer.InitializeAsync();
|
||||||
}
|
}
|
||||||
|
app.UseForwardedHeaders();
|
||||||
app.UseApplicationStaticFiles();
|
app.UseApplicationStaticFiles();
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||||
app.UseHsts();
|
app.UseHsts();
|
||||||
}
|
}
|
||||||
app.UseHttpsRedirection();
|
|
||||||
app.UseApplicationOpenApi();
|
app.UseApplicationOpenApi();
|
||||||
app.UseCors("MyAllowSpecificOrigins");
|
app.UseCors("MyAllowSpecificOrigins");
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
app.UseHttpsRedirection();
|
||||||
app.Run();
|
app.Run();
|
||||||
@@ -11,5 +11,5 @@ public interface IUserService
|
|||||||
Task<string> GenerateJwtTokenAsync(User user);
|
Task<string> GenerateJwtTokenAsync(User user);
|
||||||
Task<User?> GetUserByIdAsync(int userId);
|
Task<User?> GetUserByIdAsync(int userId);
|
||||||
Task<(bool success, string message, User? user)> FindOrCreateGitHubUserAsync(
|
Task<(bool success, string message, User? user)> FindOrCreateGitHubUserAsync(
|
||||||
string githubId, string githubLogin, string? email);
|
string githubId, string? githubName, string? email);
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-12
@@ -97,7 +97,7 @@ public class UserService(IDbContextFactory<MyDbContext> dbContextFactory, IConfi
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(bool success, string message, User? user)> FindOrCreateGitHubUserAsync(
|
public async Task<(bool success, string message, User? user)> FindOrCreateGitHubUserAsync(
|
||||||
string githubId, string githubLogin, string? email)
|
string githubId, string? githubName, string? email)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(email))
|
if (string.IsNullOrEmpty(email))
|
||||||
{
|
{
|
||||||
@@ -106,15 +106,13 @@ public class UserService(IDbContextFactory<MyDbContext> dbContextFactory, IConfi
|
|||||||
|
|
||||||
await using var context = await dbContextFactory.CreateDbContextAsync();
|
await using var context = await dbContextFactory.CreateDbContextAsync();
|
||||||
|
|
||||||
// 先尝试通过邮箱查找用户
|
|
||||||
var user = await context.Users.FirstOrDefaultAsync(u => u.Email == email);
|
var user = await context.Users.FirstOrDefaultAsync(u => u.Email == email);
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
// 用户不存在,创建新用户
|
|
||||||
user = new User
|
user = new User
|
||||||
{
|
{
|
||||||
UserName = $"{githubLogin}_{githubId.Substring(0, 5)}", // 创建唯一用户名
|
UserName = $"{githubName}",
|
||||||
Email = email,
|
Email = email,
|
||||||
PasswordHash = HashPassword(Guid.NewGuid().ToString()),
|
PasswordHash = HashPassword(Guid.NewGuid().ToString()),
|
||||||
GithubId = githubId,
|
GithubId = githubId,
|
||||||
@@ -125,15 +123,14 @@ public class UserService(IDbContextFactory<MyDbContext> dbContextFactory, IConfi
|
|||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
return (true, "GitHub用户注册成功", user);
|
return (true, "GitHub用户注册成功", user);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (string.IsNullOrEmpty(user.GithubId))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(user.GithubId))
|
user.GithubId = githubId;
|
||||||
{
|
user.UpdatedAt = DateTime.UtcNow;
|
||||||
user.GithubId = githubId;
|
await context.SaveChangesAsync();
|
||||||
user.UpdatedAt = DateTime.UtcNow;
|
|
||||||
await context.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
return (true, "GitHub用户登录成功", user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (true, "GitHub用户登录成功", user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,7 @@ http {
|
|||||||
proxy_set_header Connection keep-alive;
|
proxy_set_header Connection keep-alive;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_cache_bypass $http_upgrade;
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
proxy_ssl_verify off;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /uploads {
|
location /uploads {
|
||||||
@@ -33,6 +34,7 @@ http {
|
|||||||
proxy_set_header Connection keep-alive;
|
proxy_set_header Connection keep-alive;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_cache_bypass $http_upgrade;
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
proxy_ssl_verify off;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+29
-41
@@ -1,5 +1,5 @@
|
|||||||
import { type BaseResult, type AuthResponse, type LoginRequest, type RegisterRequest, type UserProfile } from './types';
|
import {type BaseResult, type AuthResponse, type LoginRequest, type RegisterRequest, type UserProfile} from './types';
|
||||||
import { fetchApi, BASE_URL } from './fetchClient';
|
import {fetchApi, BASE_URL} from './fetchClient';
|
||||||
|
|
||||||
// 认证数据本地存储键
|
// 认证数据本地存储键
|
||||||
const TOKEN_KEY = 'token';
|
const TOKEN_KEY = 'token';
|
||||||
@@ -96,7 +96,7 @@ export const getToken = (): string | null => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 处理GitHub OAuth回调,接收token并保存
|
// 处理GitHub OAuth回调,接收token并保存
|
||||||
export function handleOAuthCallback(): boolean {
|
export async function handleOAuthCallback(): Promise<boolean> {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const token = urlParams.get('token');
|
const token = urlParams.get('token');
|
||||||
const error = urlParams.get('error');
|
const error = urlParams.get('error');
|
||||||
@@ -104,52 +104,40 @@ export function handleOAuthCallback(): boolean {
|
|||||||
if (error) return false;
|
if (error) return false;
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
const githubUser = parseJwt(token);
|
try {
|
||||||
if (githubUser) {
|
// 保存临时token,用于API调用
|
||||||
// 保存token
|
localStorage.setItem(TOKEN_KEY, token);
|
||||||
localStorage.setItem('token', token);
|
|
||||||
|
|
||||||
// 保存用户信息
|
// 获取完整的用户信息
|
||||||
if (githubUser.unique_name && githubUser.email) {
|
const userResponse = await getCurrentUser();
|
||||||
const user: UserProfile = {
|
|
||||||
id: parseInt(githubUser.nameid),
|
if (userResponse.success && userResponse.data) {
|
||||||
userName: githubUser.unique_name,
|
// 构造完整的认证响应并保存
|
||||||
email: githubUser.email,
|
const authResponse: AuthResponse = {
|
||||||
roleName: ''
|
token: token,
|
||||||
|
user: userResponse.data
|
||||||
};
|
};
|
||||||
localStorage.setItem('user', JSON.stringify(user));
|
|
||||||
|
saveAuthData(authResponse);
|
||||||
|
|
||||||
|
// 清除URL中的token参数
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.delete('token');
|
||||||
|
window.history.replaceState({}, document.title, url.toString());
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
// 清除URL中的token参数
|
} catch (error) {
|
||||||
const url = new URL(window.location.href);
|
console.error('第三方登录处理失败:', error);
|
||||||
url.searchParams.delete('token');
|
clearAuthData(); // 清除可能部分保存的数据
|
||||||
window.history.replaceState({}, document.title, url.toString());
|
return false;
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析JWT获取用户信息
|
|
||||||
function parseJwt(token: string) {
|
|
||||||
try {
|
|
||||||
const base64Url = token.split('.')[1];
|
|
||||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
|
||||||
const jsonPayload = decodeURIComponent(
|
|
||||||
atob(base64)
|
|
||||||
.split('')
|
|
||||||
.map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
||||||
.join('')
|
|
||||||
);
|
|
||||||
return JSON.parse(jsonPayload);
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取GitHub登录URL
|
|
||||||
export function getGitHubLoginUrl(): string {
|
export function getGitHubLoginUrl(): string {
|
||||||
return `${BASE_URL}/auth/github/login?returnUrl=${window.location.origin}/api/auth/github/callback`;
|
return `${BASE_URL}/auth/github/login`;
|
||||||
}
|
}
|
||||||
+303
-277
@@ -1,305 +1,331 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, {useState, useEffect} from 'react';
|
||||||
import { Form, Input, Button, Checkbox, Typography, Row, Col, Divider, message } from 'antd';
|
import {Form, Input, Button, Checkbox, Typography, Row, Col, Divider, message} from 'antd';
|
||||||
import { UserOutlined, LockOutlined, GithubOutlined, GoogleOutlined } from '@ant-design/icons';
|
import {UserOutlined, LockOutlined, GithubOutlined, GoogleOutlined} from '@ant-design/icons';
|
||||||
import { useNavigate, Link } from 'react-router';
|
import {useNavigate, Link} from 'react-router';
|
||||||
import { login, saveAuthData, isAuthenticated, handleOAuthCallback, getGitHubLoginUrl } from '../../api';
|
import {login, saveAuthData, isAuthenticated, handleOAuthCallback, getGitHubLoginUrl} from '../../api';
|
||||||
import useIsMobile from '../../hooks/useIsMobile';
|
import useIsMobile from '../../hooks/useIsMobile';
|
||||||
|
|
||||||
const { Title, Text } = Typography;
|
const {Title, Text} = Typography;
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
const Login: React.FC = () => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (handleOAuthCallback()) {
|
const checkOAuthCallback = async () => {
|
||||||
message.success('GitHub登录成功!');
|
try {
|
||||||
navigate('/');
|
if (await handleOAuthCallback()) {
|
||||||
return;
|
message.success('第三方登录成功!');
|
||||||
}
|
navigate('/');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isAuthenticated()) {
|
if (isAuthenticated()) {
|
||||||
navigate('/');
|
navigate('/');
|
||||||
}
|
}
|
||||||
}, [navigate]);
|
} catch (error) {
|
||||||
|
console.error('处理登录回调失败:', error);
|
||||||
|
message.error('登录过程中出现错误');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onFinish = async (values: any) => {
|
checkOAuthCallback();
|
||||||
setLoading(true);
|
}, [navigate]);
|
||||||
try {
|
|
||||||
const response = await login({
|
|
||||||
email: values.email,
|
|
||||||
password: values.password
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.success && response.data) {
|
const onFinish = async (values: any) => {
|
||||||
// 保存认证信息
|
setLoading(true);
|
||||||
saveAuthData(response.data);
|
try {
|
||||||
|
const response = await login({
|
||||||
|
email: values.email,
|
||||||
|
password: values.password
|
||||||
|
});
|
||||||
|
|
||||||
// 显示成功消息
|
if (response.success && response.data) {
|
||||||
message.success(response.message || '登录成功!');
|
// 保存认证信息
|
||||||
|
saveAuthData(response.data);
|
||||||
|
|
||||||
// 跳转到首页
|
|
||||||
navigate('/');
|
|
||||||
} else {
|
|
||||||
// 显示错误消息
|
|
||||||
message.error(response.message || '登录失败,请检查账号和密码');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('登录出错:', error);
|
|
||||||
message.error('登录过程中出现错误,请稍后重试');
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGitHubLogin = () => {
|
// 显示成功消息
|
||||||
window.location.href = getGitHubLoginUrl();
|
message.success(response.message || '登录成功!');
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
// 跳转到首页
|
||||||
<Row style={{ height: '100vh', overflow: 'hidden' }}>
|
navigate('/');
|
||||||
{/* 左侧登录表单 */}
|
} else {
|
||||||
<Col
|
// 显示错误消息
|
||||||
xs={24}
|
message.error(response.message || '登录失败,请检查账号和密码');
|
||||||
md={isMobile ? 24 : 12}
|
}
|
||||||
style={{
|
} catch (error) {
|
||||||
padding: isMobile ? '20px' : '40px',
|
console.error('登录出错:', error);
|
||||||
display: 'flex',
|
message.error('登录过程中出现错误,请稍后重试');
|
||||||
flexDirection: 'column',
|
} finally {
|
||||||
justifyContent: 'center',
|
setLoading(false);
|
||||||
maxWidth: isMobile ? '100%' : '650px',
|
}
|
||||||
margin: '0 auto'
|
};
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={{
|
|
||||||
maxWidth: isMobile ? '100%' : '400px',
|
|
||||||
width: '100%',
|
|
||||||
margin: '0 auto',
|
|
||||||
paddingBottom: isMobile ? '40px' : 0
|
|
||||||
}}>
|
|
||||||
<div style={{ marginBottom: '40px', textAlign: 'center' }}>
|
|
||||||
<Title level={2} style={{
|
|
||||||
marginBottom: '8px',
|
|
||||||
fontWeight: 700,
|
|
||||||
background: 'linear-gradient(120deg, #18181b, #444444)',
|
|
||||||
WebkitBackgroundClip: 'text',
|
|
||||||
WebkitTextFillColor: 'transparent'
|
|
||||||
}}>
|
|
||||||
欢迎回到 Foxel
|
|
||||||
</Title>
|
|
||||||
<Text style={{ fontSize: '16px', color: '#666' }}>
|
|
||||||
请登录您的账户以继续使用
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Form
|
const handleGitHubLogin = () => {
|
||||||
name="login_form"
|
window.location.href = getGitHubLoginUrl();
|
||||||
initialValues={{ remember: true }}
|
};
|
||||||
onFinish={onFinish}
|
|
||||||
size="large"
|
return (
|
||||||
layout="vertical"
|
<Row style={{height: '100vh', overflow: 'hidden'}}>
|
||||||
>
|
{/* 左侧登录表单 */}
|
||||||
<Form.Item
|
<Col
|
||||||
name="email"
|
xs={24}
|
||||||
rules={[{ required: true, message: '请输入您的邮箱' }]}
|
md={isMobile ? 24 : 12}
|
||||||
|
style={{
|
||||||
|
padding: isMobile ? '20px' : '40px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
maxWidth: isMobile ? '100%' : '650px',
|
||||||
|
margin: '0 auto'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Input
|
<div style={{
|
||||||
prefix={<UserOutlined style={{ color: '#bfbfbf' }} />}
|
maxWidth: isMobile ? '100%' : '400px',
|
||||||
placeholder="邮箱"
|
width: '100%',
|
||||||
style={{
|
margin: '0 auto',
|
||||||
height: '50px',
|
paddingBottom: isMobile ? '40px' : 0
|
||||||
borderRadius: '10px',
|
}}>
|
||||||
backgroundColor: '#f8f9fa',
|
<div style={{marginBottom: '40px', textAlign: 'center'}}>
|
||||||
border: '1px solid #eaeaea'
|
<Title level={2} style={{
|
||||||
}}
|
marginBottom: '8px',
|
||||||
/>
|
fontWeight: 700,
|
||||||
</Form.Item>
|
background: 'linear-gradient(120deg, #18181b, #444444)',
|
||||||
|
WebkitBackgroundClip: 'text',
|
||||||
|
WebkitTextFillColor: 'transparent'
|
||||||
|
}}>
|
||||||
|
欢迎回到 Foxel
|
||||||
|
</Title>
|
||||||
|
<Text style={{fontSize: '16px', color: '#666'}}>
|
||||||
|
请登录您的账户以继续使用
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Form.Item
|
<Form
|
||||||
name="password"
|
name="login_form"
|
||||||
rules={[{ required: true, message: '请输入您的密码' }]}
|
initialValues={{remember: true}}
|
||||||
>
|
onFinish={onFinish}
|
||||||
<Input.Password
|
size="large"
|
||||||
prefix={<LockOutlined style={{ color: '#bfbfbf' }} />}
|
layout="vertical"
|
||||||
placeholder="密码"
|
>
|
||||||
style={{
|
<Form.Item
|
||||||
height: '50px',
|
name="email"
|
||||||
borderRadius: '10px',
|
rules={[{required: true, message: '请输入您的邮箱'}]}
|
||||||
backgroundColor: '#f8f9fa',
|
>
|
||||||
border: '1px solid #eaeaea'
|
<Input
|
||||||
}}
|
prefix={<UserOutlined style={{color: '#bfbfbf'}}/>}
|
||||||
/>
|
placeholder="邮箱"
|
||||||
</Form.Item>
|
style={{
|
||||||
|
height: '50px',
|
||||||
|
borderRadius: '10px',
|
||||||
|
backgroundColor: '#f8f9fa',
|
||||||
|
border: '1px solid #eaeaea'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
name="password"
|
||||||
<Checkbox name="remember">记住我</Checkbox>
|
rules={[{required: true, message: '请输入您的密码'}]}
|
||||||
<a href="#forgot" style={{ color: '#18181b' }}>忘记密码?</a>
|
>
|
||||||
</div>
|
<Input.Password
|
||||||
</Form.Item>
|
prefix={<LockOutlined style={{color: '#bfbfbf'}}/>}
|
||||||
|
placeholder="密码"
|
||||||
|
style={{
|
||||||
|
height: '50px',
|
||||||
|
borderRadius: '10px',
|
||||||
|
backgroundColor: '#f8f9fa',
|
||||||
|
border: '1px solid #eaeaea'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<div style={{display: 'flex', justifyContent: 'space-between'}}>
|
||||||
type="primary"
|
<Checkbox name="remember">记住我</Checkbox>
|
||||||
htmlType="submit"
|
<a href="#forgot" style={{color: '#18181b'}}>忘记密码?</a>
|
||||||
loading={loading}
|
</div>
|
||||||
style={{
|
</Form.Item>
|
||||||
width: '100%',
|
|
||||||
height: '50px',
|
|
||||||
borderRadius: '10px',
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '16px',
|
|
||||||
boxShadow: '0 4px 12px rgba(0,0,0,0.15)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
登录
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Divider plain style={{ color: '#999', fontSize: '14px' }}>
|
<Form.Item>
|
||||||
或使用以下方式登录
|
<Button
|
||||||
</Divider>
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
loading={loading}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '50px',
|
||||||
|
borderRadius: '10px',
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: '16px',
|
||||||
|
boxShadow: '0 4px 12px rgba(0,0,0,0.15)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '20px', margin: '20px 0' }}>
|
<Divider plain style={{color: '#999', fontSize: '14px'}}>
|
||||||
<Button
|
或使用以下方式登录
|
||||||
icon={<GithubOutlined />}
|
</Divider>
|
||||||
size="large"
|
|
||||||
shape="circle"
|
|
||||||
onClick={handleGitHubLogin}
|
|
||||||
style={{
|
|
||||||
backgroundColor: '#f6f6f6',
|
|
||||||
border: 'none',
|
|
||||||
boxShadow: '0 2px 8px rgba(0,0,0,0.06)'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
icon={<GoogleOutlined />}
|
|
||||||
size="large"
|
|
||||||
shape="circle"
|
|
||||||
style={{
|
|
||||||
backgroundColor: '#f6f6f6',
|
|
||||||
border: 'none',
|
|
||||||
boxShadow: '0 2px 8px rgba(0,0,0,0.06)'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ textAlign: 'center', marginTop: '30px' }}>
|
<div style={{display: 'flex', justifyContent: 'center', gap: '20px', margin: '20px 0'}}>
|
||||||
<Text style={{ color: '#666' }}>
|
<Button
|
||||||
还没有账户? <Link to="/register" style={{ color: '#18181b', fontWeight: 500 }}>立即注册</Link>
|
icon={<GithubOutlined/>}
|
||||||
</Text>
|
size="large"
|
||||||
<div style={{ marginTop: '15px' }}>
|
shape="circle"
|
||||||
<Link to="/anonymous" style={{ color: '#666' }}>
|
onClick={handleGitHubLogin}
|
||||||
<Button type="link" style={{ padding: '0', fontWeight: 500 }}>匿名图床</Button>
|
style={{
|
||||||
</Link>
|
backgroundColor: '#f6f6f6',
|
||||||
</div>
|
border: 'none',
|
||||||
</div>
|
boxShadow: '0 2px 8px rgba(0,0,0,0.06)'
|
||||||
</Form>
|
}}
|
||||||
</div>
|
/>
|
||||||
</Col>
|
<Button
|
||||||
|
icon={<GoogleOutlined/>}
|
||||||
|
size="large"
|
||||||
|
shape="circle"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#f6f6f6',
|
||||||
|
border: 'none',
|
||||||
|
boxShadow: '0 2px 8px rgba(0,0,0,0.06)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 右侧视觉区域 - 仅在非移动设备上显示 */}
|
<div style={{textAlign: 'center', marginTop: '30px'}}>
|
||||||
{!isMobile && (
|
<Text style={{color: '#666'}}>
|
||||||
<Col md={12} style={{
|
还没有账户? <Link to="/register"
|
||||||
background: 'linear-gradient(135deg, #18181b 0%, #444444 100%)',
|
style={{color: '#18181b', fontWeight: 500}}>立即注册</Link>
|
||||||
display: 'flex',
|
</Text>
|
||||||
alignItems: 'center',
|
<div style={{marginTop: '15px'}}>
|
||||||
justifyContent: 'center',
|
<Link to="/anonymous" style={{color: '#666'}}>
|
||||||
position: 'relative',
|
<Button type="link" style={{padding: '0', fontWeight: 500}}>匿名图床</Button>
|
||||||
overflow: 'hidden',
|
</Link>
|
||||||
height: '100vh'
|
</div>
|
||||||
}}>
|
</div>
|
||||||
<div style={{
|
</Form>
|
||||||
position: 'absolute',
|
</div>
|
||||||
width: '600px',
|
</Col>
|
||||||
height: '600px',
|
|
||||||
background: 'radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 70%)',
|
|
||||||
top: '20%',
|
|
||||||
left: '30%'
|
|
||||||
}}></div>
|
|
||||||
|
|
||||||
<div style={{
|
{/* 右侧视觉区域 - 仅在非移动设备上显示 */}
|
||||||
position: 'absolute',
|
{!isMobile && (
|
||||||
width: '400px',
|
<Col md={12} style={{
|
||||||
height: '400px',
|
background: 'linear-gradient(135deg, #18181b 0%, #444444 100%)',
|
||||||
background: 'radial-gradient(circle, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0) 70%)',
|
display: 'flex',
|
||||||
bottom: '10%',
|
alignItems: 'center',
|
||||||
right: '20%'
|
justifyContent: 'center',
|
||||||
}}></div>
|
position: 'relative',
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: '100vh'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
width: '600px',
|
||||||
|
height: '600px',
|
||||||
|
background: 'radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 70%)',
|
||||||
|
top: '20%',
|
||||||
|
left: '30%'
|
||||||
|
}}></div>
|
||||||
|
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '40px',
|
position: 'absolute',
|
||||||
textAlign: 'center',
|
width: '400px',
|
||||||
color: 'white',
|
height: '400px',
|
||||||
position: 'relative',
|
background: 'radial-gradient(circle, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0) 70%)',
|
||||||
zIndex: 1,
|
bottom: '10%',
|
||||||
maxWidth: '500px'
|
right: '20%'
|
||||||
}}>
|
}}></div>
|
||||||
<Title level={2} style={{
|
|
||||||
color: 'white',
|
|
||||||
marginBottom: '25px',
|
|
||||||
fontWeight: 700,
|
|
||||||
letterSpacing: '1px'
|
|
||||||
}}>
|
|
||||||
图片管理新方式
|
|
||||||
</Title>
|
|
||||||
<Text style={{
|
|
||||||
fontSize: '18px',
|
|
||||||
color: 'rgba(255,255,255,0.8)',
|
|
||||||
lineHeight: '1.8',
|
|
||||||
display: 'block',
|
|
||||||
marginBottom: '30px'
|
|
||||||
}}>
|
|
||||||
Foxel 提供高效、直观的图片管理体验,让您轻松整理、查找和分享珍贵的视觉记忆。
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{/* 图片管理界面预览 */}
|
<div style={{
|
||||||
<div style={{
|
padding: '40px',
|
||||||
width: '100%',
|
textAlign: 'center',
|
||||||
height: '300px',
|
color: 'white',
|
||||||
background: 'rgba(255,255,255,0.1)',
|
position: 'relative',
|
||||||
borderRadius: '20px',
|
zIndex: 1,
|
||||||
boxShadow: '0 20px 40px rgba(0,0,0,0.3)',
|
maxWidth: '500px'
|
||||||
backdropFilter: 'blur(20px)',
|
}}>
|
||||||
border: '1px solid rgba(255,255,255,0.2)',
|
<Title level={2} style={{
|
||||||
position: 'relative',
|
color: 'white',
|
||||||
overflow: 'hidden'
|
marginBottom: '25px',
|
||||||
}}>
|
fontWeight: 700,
|
||||||
<div style={{
|
letterSpacing: '1px'
|
||||||
position: 'absolute',
|
}}>
|
||||||
top: '12px',
|
图片管理新方式
|
||||||
left: '12px',
|
</Title>
|
||||||
right: '12px',
|
<Text style={{
|
||||||
height: '30px',
|
fontSize: '18px',
|
||||||
borderRadius: '8px 8px 0 0',
|
color: 'rgba(255,255,255,0.8)',
|
||||||
display: 'flex',
|
lineHeight: '1.8',
|
||||||
alignItems: 'center',
|
display: 'block',
|
||||||
gap: '8px',
|
marginBottom: '30px'
|
||||||
paddingLeft: '12px'
|
}}>
|
||||||
}}>
|
Foxel 提供高效、直观的图片管理体验,让您轻松整理、查找和分享珍贵的视觉记忆。
|
||||||
<div style={{ width: '10px', height: '10px', borderRadius: '50%', background: 'rgba(255,255,255,0.6)' }} />
|
</Text>
|
||||||
<div style={{ width: '10px', height: '10px', borderRadius: '50%', background: 'rgba(255,255,255,0.6)' }} />
|
|
||||||
<div style={{ width: '10px', height: '10px', borderRadius: '50%', background: 'rgba(255,255,255,0.6)' }} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{
|
{/* 图片管理界面预览 */}
|
||||||
position: 'absolute',
|
<div style={{
|
||||||
top: '50%',
|
width: '100%',
|
||||||
left: '50%',
|
height: '300px',
|
||||||
transform: 'translate(-50%, -50%)',
|
background: 'rgba(255,255,255,0.1)',
|
||||||
fontSize: '24px',
|
borderRadius: '20px',
|
||||||
fontWeight: 'bold',
|
boxShadow: '0 20px 40px rgba(0,0,0,0.3)',
|
||||||
color: 'rgba(255,255,255,0.7)'
|
backdropFilter: 'blur(20px)',
|
||||||
}}>
|
border: '1px solid rgba(255,255,255,0.2)',
|
||||||
Foxel 界面预览
|
position: 'relative',
|
||||||
</div>
|
overflow: 'hidden'
|
||||||
</div>
|
}}>
|
||||||
</div>
|
<div style={{
|
||||||
</Col>
|
position: 'absolute',
|
||||||
)}
|
top: '12px',
|
||||||
</Row>
|
left: '12px',
|
||||||
);
|
right: '12px',
|
||||||
|
height: '30px',
|
||||||
|
borderRadius: '8px 8px 0 0',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
paddingLeft: '12px'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
width: '10px',
|
||||||
|
height: '10px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'rgba(255,255,255,0.6)'
|
||||||
|
}}/>
|
||||||
|
<div style={{
|
||||||
|
width: '10px',
|
||||||
|
height: '10px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'rgba(255,255,255,0.6)'
|
||||||
|
}}/>
|
||||||
|
<div style={{
|
||||||
|
width: '10px',
|
||||||
|
height: '10px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'rgba(255,255,255,0.6)'
|
||||||
|
}}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
fontSize: '24px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: 'rgba(255,255,255,0.7)'
|
||||||
|
}}>
|
||||||
|
Foxel 界面预览
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Login;
|
export default Login;
|
||||||
@@ -13,16 +13,24 @@ const Register: React.FC = () => {
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 处理可能的OAuth回调
|
const checkOAuthCallback = async () => {
|
||||||
if (handleOAuthCallback()) {
|
try {
|
||||||
message.success('使用GitHub账号注册成功!');
|
if (await handleOAuthCallback()) {
|
||||||
navigate('/');
|
message.success('使用GitHub账号注册成功!');
|
||||||
return;
|
navigate('/');
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isAuthenticated()) {
|
if (isAuthenticated()) {
|
||||||
navigate('/');
|
navigate('/');
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('处理登录回调失败:', error);
|
||||||
|
message.error('登录过程中出现错误');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkOAuthCallback();
|
||||||
}, [navigate]);
|
}, [navigate]);
|
||||||
|
|
||||||
const onFinish = async (values: any) => {
|
const onFinish = async (values: any) => {
|
||||||
|
|||||||
@@ -138,12 +138,14 @@ const SystemConfig: React.FC = () => {
|
|||||||
groupName="Authentication"
|
groupName="Authentication"
|
||||||
configs={{
|
configs={{
|
||||||
"GitHubClientId": configs.Authentication?.["GitHubClientId"] || '',
|
"GitHubClientId": configs.Authentication?.["GitHubClientId"] || '',
|
||||||
"GitHubClientSecret": configs.Authentication?.["GitHubClientSecret"] || ''
|
"GitHubClientSecret": configs.Authentication?.["GitHubClientSecret"] || '',
|
||||||
|
"GitHubCallbackUrl": configs.Authentication?.["GitHubCallbackUrl"] || ''
|
||||||
}}
|
}}
|
||||||
onSave={(_group, key, value) => handleSaveConfig('Authentication', key, value)}
|
onSave={(_group, key, value) => handleSaveConfig('Authentication', key, value)}
|
||||||
descriptions={{
|
descriptions={{
|
||||||
"GitHubClientId": 'GitHub OAuth 应用客户端ID',
|
"GitHubClientId": 'GitHub OAuth 应用客户端ID',
|
||||||
"GitHubClientSecret": 'GitHub OAuth 应用客户端密钥'
|
"GitHubClientSecret": 'GitHub OAuth 应用客户端密钥',
|
||||||
|
"GitHubCallbackUrl": 'GitHub OAuth 认证回调地址'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|||||||
Reference in New Issue
Block a user