auth-register
约 2043 字大约 7 分钟
2025-08-11
概述
auth-register 边缘函数负责处理用户注册请求,创建新用户账户,并返回用户信息和会话数据。
文件位置
supabase/functions/auth-register/index.tsAPI 端点
- 方法:
POST - 路径:
/auth-register - Content-Type:
application/json
请求参数
请求体
| 参数名 | 类型 | 必需 | 默认值 | 描述 |
|---|---|---|---|---|
| string | ✅ | - | 用户邮箱地址 | |
| password | string | ✅ | - | 用户密码(最少6位) |
| full_name | string | ✅ | - | 用户真实姓名 |
| role | string | ❌ | 'student' | 用户角色:admin/student/teacher |
| phone | string | ❌ | null | 用户手机号 |
请求示例
{
"email": "newuser@example.com",
"password": "password123",
"full_name": "张三",
"role": "student",
"phone": "13800138000"
}响应格式
成功响应 (200)
邮箱已确认
{
"success": true,
"message": "注册成功",
"data": {
"user": {
"id": "uuid",
"email": "newuser@example.com",
"email_confirmed_at": "2025-08-11T09:00:00Z",
// ... 其他用户字段
},
"session": {
"access_token": "jwt_token",
"refresh_token": "refresh_token",
"expires_in": 3600,
// ... 其他会话字段
},
"userInfo": {
"id": "uuid",
"email": "newuser@example.com",
"full_name": "张三",
"role": "student",
"avatar_url": null,
"phone": "13800138000",
"is_active": true
}
}
}邮箱待确认
{
"success": true,
"message": "注册成功,请检查您的邮箱以确认账户",
"data": {
"user": {
"id": "uuid",
"email": "newuser@example.com",
"email_confirmed_at": null,
// ... 其他用户字段
},
"session": null,
"userInfo": {
"id": "uuid",
"email": "newuser@example.com",
"full_name": "张三",
"role": "student",
"avatar_url": null,
"phone": "13800138000",
"is_active": true
}
}
}错误响应
验证错误 (400)
{
"success": false,
"error": "缺少必填字段: email, password, full_name"
}邮箱格式错误 (400)
{
"success": false,
"error": "邮箱格式不正确"
}密码强度不足 (400)
{
"success": false,
"error": "密码长度至少为6位"
}用户角色错误 (400)
{
"success": false,
"error": "用户角色不正确"
}手机号格式错误 (400)
{
"success": false,
"error": "手机号格式不正确"
}邮箱已存在 (409)
{
"success": false,
"error": "该邮箱已被注册"
}服务器错误 (500)
{
"success": false,
"error": "注册失败"
}业务流程
1. 请求预处理
- 处理 CORS 预检请求
- 解析请求体中的 JSON 数据
- 设置默认角色为 'student'
2. 数据验证
- 验证必填字段:
email、password、full_name - 验证邮箱格式:使用正则表达式验证
- 验证密码强度:最少6位字符
- 验证用户角色:admin/student/teacher
- 验证手机号格式:中国大陆手机号(可选)
3. 用户存在性检查
- 在
user_info表中检查邮箱是否已存在 - 已存在返回 409 冲突状态码
4. 用户账户创建
- 调用
supabase.auth.signUp()创建认证账户 - 传递用户元数据:full_name、role、phone
- 创建失败返回 400 状态码
5. 用户信息处理
- 尝试从
user_info表获取用户信息 - 如果触发器未执行,手动插入用户信息记录
- 确保用户信息完整性
6. 响应返回
- 根据邮箱确认状态返回相应消息
- 成功时返回用户信息、会话数据和扩展信息
依赖模块
shared/index.ts
createSupabaseClient()- 创建 Supabase 客户端handleCors()- 处理 CORS 请求createSuccessResponse()- 创建成功响应createValidationError()- 创建验证错误响应createConflictResponse()- 创建冲突响应createServerErrorResponse()- 创建服务器错误响应safeAsyncHandler()- 安全异步处理器validateRequiredFields()- 验证必填字段isValidEmail()- 邮箱格式验证isValidPassword()- 密码强度验证isValidUserRole()- 用户角色验证isValidPhoneNumber()- 手机号格式验证
数据库操作
检查用户存在性
SELECT id FROM user_info WHERE email = $1获取用户信息
SELECT id, email, full_name, role, avatar_url, phone, is_active
FROM user_info
WHERE id = $1手动插入用户信息
INSERT INTO user_info (id, email, full_name, role, phone, is_active)
VALUES ($1, $2, $3, $4, $5, true)数据库触发器集成
handle_new_user() 函数
- 在
auth.users表插入新记录时自动触发 - 从
raw_user_meta_data提取用户信息 - 自动创建
user_info表记录 - 包含错误处理和日志记录
触发器逻辑
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE PROCEDURE public.handle_new_user();安全特性
1. 输入验证
- 必填字段检查
- 邮箱格式验证
- 密码强度验证
- 用户角色白名单验证
- 手机号格式验证
2. 数据唯一性
- 邮箱地址唯一性检查
- 防止重复注册
- 数据库约束保护
3. 权限控制
- 默认用户角色设置
- 账户激活状态管理
- 行级安全策略(RLS)
4. 错误处理
- 详细的验证错误消息
- 统一错误响应格式
- 敏感信息保护
用户角色系统
角色类型
- admin: 系统管理员,拥有最高权限
- teacher: 教师用户,可以管理课程和学生
- student: 学生用户,默认角色,基础权限
角色验证
function isValidUserRole(role: string): role is 'admin' | 'student' | 'teacher' {
return ['admin', 'student', 'teacher'].includes(role)
}使用示例
cURL 请求
# 本地开发环境
curl -X POST http://127.0.0.1:54321/functions/v1/auth-register \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ANON_KEY" \
-d '{
"email": "newuser@example.com",
"password": "password123",
"full_name": "张三",
"role": "student",
"phone": "13800138000"
}'
# 生产环境
curl -X POST https://your-project.supabase.co/functions/v1/auth-register \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ANON_KEY" \
-d '{
"email": "newuser@example.com",
"password": "password123",
"full_name": "张三",
"role": "student",
"phone": "13800138000"
}'
# 最简注册(只需必填字段)
curl -X POST http://127.0.0.1:54321/functions/v1/auth-register \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ANON_KEY" \
-d '{
"email": "newuser@example.com",
"password": "password123",
"full_name": "张三"
}'JavaScript 客户端
使用 Supabase 客户端(推荐)
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'your-project-url',
'your-anon-key'
)
// 完整注册信息
const { data, error } = await supabase.functions.invoke('auth-register', {
body: {
email: 'newuser@example.com',
password: 'password123',
full_name: '张三',
role: 'student',
phone: '13800138000'
}
})
if (error) {
console.error('注册失败:', error)
} else {
console.log('注册成功:', data)
// 处理注册结果
const { user, session, userInfo } = data
if (session) {
// 邮箱已确认,可以直接登录
console.log('账户已激活,可以使用')
} else {
// 需要邮箱确认
console.log('请检查邮箱并确认账户')
}
}Nuxt.js 集成示例
<script setup>
const supabase = useSupabaseClient()
const router = useRouter()
const registerForm = reactive({
email: '',
password: '',
confirmPassword: '',
full_name: '',
role: 'student',
phone: ''
})
const loading = ref(false)
const message = ref('')
const register = async () => {
if (registerForm.password !== registerForm.confirmPassword) {
message.value = '两次密码输入不一致'
return
}
loading.value = true
message.value = ''
try {
const { data, error } = await supabase.functions.invoke('auth-register', {
body: {
email: registerForm.email,
password: registerForm.password,
full_name: registerForm.full_name,
role: registerForm.role,
phone: registerForm.phone || undefined
}
})
if (error) {
throw error
}
if (data.session) {
// 自动登录成功
message.value = '注册成功!'
await router.push('/dashboard')
} else {
// 需要邮箱确认
message.value = '注册成功!请检查您的邮箱以确认账户。'
}
} catch (error) {
message.value = error.message || '注册失败,请重试'
} finally {
loading.value = false
}
}
</script>
<template>
<form @submit.prevent="register">
<div>
<label>邮箱</label>
<input v-model="registerForm.email" type="email" required>
</div>
<div>
<label>密码</label>
<input v-model="registerForm.password" type="password" minlength="6" required>
</div>
<div>
<label>确认密码</label>
<input v-model="registerForm.confirmPassword" type="password" required>
</div>
<div>
<label>姓名</label>
<input v-model="registerForm.full_name" type="text" required>
</div>
<div>
<label>角色</label>
<select v-model="registerForm.role">
<option value="student">学生</option>
<option value="teacher">教师</option>
</select>
</div>
<div>
<label>手机号(可选)</label>
<input v-model="registerForm.phone" type="tel" pattern="1[3-9]\d{9}">
</div>
<button type="submit" :disabled="loading">
{{ loading ? '注册中...' : '注册' }}
</button>
<p v-if="message" :class="{ error: message.includes('失败') }">
{{ message }}
</p>
</form>
</template>
<style scoped>
.error {
color: red;
}
</style>Python 客户端
使用 Supabase Python 客户端(推荐)
from supabase import create_client, Client
# 初始化客户端
url = "your-project-url"
key = "your-anon-key"
supabase: Client = create_client(url, key)
def register_user(email, password, full_name, role="student", phone=None):
"""
用户注册函数
Args:
email: 用户邮箱
password: 用户密码
full_name: 用户姓名
role: 用户角色 (默认为 student)
phone: 手机号 (可选)
"""
try:
# 构建请求数据
register_data = {
"email": email,
"password": password,
"full_name": full_name,
"role": role
}
if phone:
register_data["phone"] = phone
# 调用边缘函数
response = supabase.functions.invoke(
"auth-register",
invoke_options={
"body": register_data
}
)
# 检查响应
if response.status_code == 200:
result = response.json()
if result.get("success"):
print("注册成功:")
user_data = result["data"]
print(f"用户ID: {user_data['user']['id']}")
print(f"邮箱: {user_data['user']['email']}")
if user_data['session']:
print("账户已激活,可以直接使用")
print(f"访问令牌: {user_data['session']['access_token']}")
return user_data
else:
print("请检查邮箱并确认账户")
return user_data
else:
print(f"注册失败: {result.get('error')}")
return None
else:
print(f"HTTP错误: {response.status_code}")
return None
except Exception as e:
print(f"调用失败: {str(e)}")
return None
# 使用示例
result = register_user(
email="newuser@example.com",
password="password123",
full_name="张三",
role="teacher",
phone="13800138000"
)
if result:
print("注册流程完成")FastAPI 应用集成示例
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, EmailStr
from supabase import create_client, Client
from typing import Optional
import os
app = FastAPI()
# 初始化 Supabase 客户端
supabase: Client = create_client(
os.getenv("SUPABASE_URL"),
os.getenv("SUPABASE_ANON_KEY")
)
class UserRegisterRequest(BaseModel):
email: EmailStr
password: str
full_name: str
role: Optional[str] = "student"
phone: Optional[str] = None
class UserRegisterResponse(BaseModel):
success: bool
message: str
needs_confirmation: bool = False
@app.post("/api/register", response_model=UserRegisterResponse)
async def register_user(user_data: UserRegisterRequest):
"""用户注册接口"""
try:
# 构建请求数据
register_data = {
"email": user_data.email,
"password": user_data.password,
"full_name": user_data.full_name,
"role": user_data.role
}
if user_data.phone:
register_data["phone"] = user_data.phone
# 调用边缘函数
response = supabase.functions.invoke(
"auth-register",
invoke_options={
"body": register_data
}
)
if response.status_code == 200:
result = response.json()
if result.get("success"):
return UserRegisterResponse(
success=True,
message=result.get("message", "注册成功"),
needs_confirmation=result["data"]["session"] is None
)
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=result.get("error")
)
else:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="注册服务暂时不可用"
)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"注册失败: {str(e)}"
)
@app.get("/")
async def root():
return {"message": "Evoliant API 服务"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)版权所有
版权归属:Evoliant
许可证:MIT