add login

This commit is contained in:
maojindao55
2025-03-25 06:36:14 +08:00
parent 6e286682d3
commit 6c495ac631
7 changed files with 532 additions and 11 deletions

157
functions/api/login.ts Normal file
View File

@@ -0,0 +1,157 @@
interface Env {
bgkv: KVNamespace;
JWT_SECRET: string;
}
export const onRequestPost: PagesFunction<Env> = async (context) => {
try {
const { request, env } = context;
// 获取请求体
const body = await request.json();
const { phone, code } = body;
// 验证手机号格式
if (!phone || !/^1[3-9]\d{9}$/.test(phone)) {
return new Response(
JSON.stringify({
success: false,
message: '无效的手机号码'
}),
{
status: 400,
headers: {
'Content-Type': 'application/json',
},
}
);
}
// 验证码格式检查
if (!code || !/^\d{6}$/.test(code)) {
return new Response(
JSON.stringify({
success: false,
message: '验证码格式错误'
}),
{
status: 400,
headers: {
'Content-Type': 'application/json',
},
}
);
}
// 从 KV 中获取存储的验证码
const storedCode = await env.bgkv.get(`sms:${phone}`);
if (!storedCode || storedCode !== code) {
return new Response(
JSON.stringify({
success: false,
message: '验证码错误或已过期'
}),
{
status: 400,
headers: {
'Content-Type': 'application/json',
},
}
);
}
// 验证成功,生成 JWT token传入 env
const token = await generateToken(phone, env);
// 删除验证码
await env.bgkv.delete(`sms:${phone}`);
return new Response(
JSON.stringify({
success: true,
message: '登录成功',
data: {
token,
phone
}
}),
{
status: 200,
headers: {
'Content-Type': 'application/json',
},
}
);
} catch (error) {
console.error(error);
return new Response(
JSON.stringify({
success: false,
message: '服务器错误'
}),
{
status: 500,
headers: {
'Content-Type': 'application/json',
},
}
);
}
};
// 修改为 async 函数
async function generateToken(phone: string, env: Env): Promise<string> {
const header = {
alg: 'HS256',
typ: 'JWT'
};
const payload = {
phone,
exp: Math.floor(Date.now() / 1000) + (7 * 24 * 60 * 60), // 7天过期
iat: Math.floor(Date.now() / 1000)
};
// Base64Url 编码
const encodeBase64Url = (data: object) => {
return btoa(JSON.stringify(data))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
};
// 生成签名
const generateSignature = async (input: string, secret: string) => {
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign(
'HMAC',
key,
encoder.encode(input)
);
return btoa(String.fromCharCode(...new Uint8Array(signature)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
};
const headerEncoded = encodeBase64Url(header);
const payloadEncoded = encodeBase64Url(payload);
const signature = await generateSignature(
`${headerEncoded}.${payloadEncoded}`,
env.JWT_SECRET
);
return `${headerEncoded}.${payloadEncoded}.${signature}`;
}

72
functions/api/sendCode.ts Normal file
View File

@@ -0,0 +1,72 @@
interface Env {
bgkv: KVNamespace;
}
export const onRequestPost: PagesFunction<Env> = async (context) => {
try {
const { request, env } = context;
// 获取请求体
const body = await request.json();
const { phone } = body;
// 验证手机号格式
if (!phone || !/^1[3-9]\d{9}$/.test(phone)) {
return new Response(
JSON.stringify({
success: false,
message: '无效的手机号码'
}),
{
status: 400,
headers: {
'Content-Type': 'application/json',
},
}
);
}
// 生成6位随机验证码
let verificationCode = Math.floor(100000 + Math.random() * 900000).toString();
// 使用 CF_PAGES_ENVIRONMENT 判断环境
// 值为 'production' 或 'preview'
if (env.CF_PAGES_ENVIRONMENT !== 'production') {
verificationCode = '123456';
}
// 将验证码存储到 KV 中设置5分钟过期
await env.bgkv.put(`sms:${phone}`, verificationCode, {
expirationTtl: 300 // 5分钟过期
});
console.log(env.CF_PAGES_ENVIRONMENT, await env.bgkv.get(`sms:${phone}`));
return new Response(
JSON.stringify({
success: true,
message: '验证码发送成功',
// 注意:实际生产环境不应该返回验证码
code: verificationCode // 仅用于测试
}),
{
status: 200,
headers: {
'Content-Type': 'application/json',
},
}
);
} catch (error) {
console.error(error);
return new Response(
JSON.stringify({
success: false,
message: '服务器错误'
}),
{
status: 500,
headers: {
'Content-Type': 'application/json',
},
}
);
}
};

View File

@@ -0,0 +1,60 @@
interface Env {
JWT_SECRET: string;
}
export async function verifyToken(token: string, env: Env) {
try {
const [headerB64, payloadB64, signature] = token.split('.');
// 验证签名
const input = `${headerB64}.${payloadB64}`;
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(env.JWT_SECRET),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
);
const signatureBytes = Uint8Array.from(atob(
signature.replace(/-/g, '+').replace(/_/g, '/')
), c => c.charCodeAt(0));
const isValid = await crypto.subtle.verify(
'HMAC',
key,
signatureBytes,
encoder.encode(input)
);
if (!isValid) {
throw new Error('Invalid signature');
}
// 解码 payload
const payload = JSON.parse(atob(
payloadB64.replace(/-/g, '+').replace(/_/g, '/')
));
// 检查过期时间
if (payload.exp < Math.floor(Date.now() / 1000)) {
throw new Error('Token expired');
}
return payload;
} catch (error) {
throw new Error('Invalid token');
}
}
// 中间件函数
export const authMiddleware = async (request: Request, env: Env) => {
const authHeader = request.headers.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new Error('No token provided');
}
const token = authHeader.split(' ')[1];
return await verifyToken(token, env);
};