feat: Implement account management center with authentication and subscription features, supported by new backend, i18n, and Supabase integration.
This commit is contained in:
@@ -28,6 +28,8 @@ ENV=production
|
|||||||
POLYWEATHER_MAP_URL=https://polyweather-pro.vercel.app/
|
POLYWEATHER_MAP_URL=https://polyweather-pro.vercel.app/
|
||||||
# Unified Auth (Supabase + Google/Email)
|
# Unified Auth (Supabase + Google/Email)
|
||||||
POLYWEATHER_AUTH_ENABLED=false
|
POLYWEATHER_AUTH_ENABLED=false
|
||||||
|
# If true: website APIs require login; if false: guest access, login optional.
|
||||||
|
POLYWEATHER_AUTH_REQUIRED=false
|
||||||
# If true, authenticated users must also have an active row in `subscriptions`.
|
# If true, authenticated users must also have an active row in `subscriptions`.
|
||||||
POLYWEATHER_AUTH_REQUIRE_SUBSCRIPTION=false
|
POLYWEATHER_AUTH_REQUIRE_SUBSCRIPTION=false
|
||||||
SUPABASE_URL=
|
SUPABASE_URL=
|
||||||
|
|||||||
@@ -37,6 +37,8 @@
|
|||||||
NEXT_PUBLIC_SUPABASE_URL=
|
NEXT_PUBLIC_SUPABASE_URL=
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||||||
POLYWEATHER_AUTH_ENABLED=true
|
POLYWEATHER_AUTH_ENABLED=true
|
||||||
|
# true: 强制登录;false: 游客可用(可选登录)
|
||||||
|
POLYWEATHER_AUTH_REQUIRED=false
|
||||||
POLYWEATHER_API_BASE_URL=http://<backend-host>:8000
|
POLYWEATHER_API_BASE_URL=http://<backend-host>:8000
|
||||||
POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN=
|
POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN=
|
||||||
```
|
```
|
||||||
@@ -45,6 +47,8 @@ POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN=
|
|||||||
|
|
||||||
```env
|
```env
|
||||||
POLYWEATHER_AUTH_ENABLED=true
|
POLYWEATHER_AUTH_ENABLED=true
|
||||||
|
# true: 后端 API 强制鉴权;false: 游客可访问,若带会话则自动识别用户
|
||||||
|
POLYWEATHER_AUTH_REQUIRED=false
|
||||||
POLYWEATHER_AUTH_REQUIRE_SUBSCRIPTION=false
|
POLYWEATHER_AUTH_REQUIRE_SUBSCRIPTION=false
|
||||||
SUPABASE_URL=
|
SUPABASE_URL=
|
||||||
SUPABASE_ANON_KEY=
|
SUPABASE_ANON_KEY=
|
||||||
@@ -63,13 +67,14 @@ POLYWEATHER_BOT_USE_SUPABASE_ENTITLEMENT=true
|
|||||||
|
|
||||||
## 5. entitlement 策略
|
## 5. entitlement 策略
|
||||||
|
|
||||||
- `POLYWEATHER_AUTH_ENABLED=true`:要求请求携带有效 Supabase 用户会话。
|
- `POLYWEATHER_AUTH_ENABLED=true`:启用 Supabase 登录能力(Google/邮箱)。
|
||||||
- `POLYWEATHER_AUTH_REQUIRE_SUBSCRIPTION=true`:额外要求 `subscriptions` 表里存在有效 `active` 记录。
|
- `POLYWEATHER_AUTH_REQUIRED=true`:网站与后端 API 强制登录。
|
||||||
|
- `POLYWEATHER_AUTH_REQUIRED=false`:游客可访问全部功能,用户可主动登录。
|
||||||
|
- `POLYWEATHER_AUTH_REQUIRE_SUBSCRIPTION=true`:在强制鉴权模式下,额外要求 `subscriptions` 表里存在有效 `active` 记录。
|
||||||
|
|
||||||
## 6. 验证
|
## 6. 验证
|
||||||
|
|
||||||
1. 访问 `/auth/login`,测试 Google 一键登录。
|
1. 访问 `/auth/login`,测试 Google 一键登录。
|
||||||
2. 登录后访问首页,确认页面可用。
|
2. `POLYWEATHER_AUTH_REQUIRED=false` 时,未登录访问首页与 `/api/cities` 应返回 200。
|
||||||
3. 调用 `/api/cities`,确认返回 200。
|
3. 登录后访问 `/api/auth/me`,应返回 `authenticated=true` 与 `user_id`。
|
||||||
4. 退出登录后再次访问,确认被重定向到登录页。
|
4. `POLYWEATHER_AUTH_REQUIRED=true` 时,未登录访问受保护接口应返回 401 或跳转登录页。
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ POLYWEATHER_API_BASE_URL=http://127.0.0.1:8000
|
|||||||
NEXT_PUBLIC_SUPABASE_URL=
|
NEXT_PUBLIC_SUPABASE_URL=
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||||||
POLYWEATHER_AUTH_ENABLED=false
|
POLYWEATHER_AUTH_ENABLED=false
|
||||||
|
# If true: middleware forces login; if false: optional login mode (guests allowed).
|
||||||
|
POLYWEATHER_AUTH_REQUIRED=false
|
||||||
# Optional dashboard guard (Next.js middleware)
|
# Optional dashboard guard (Next.js middleware)
|
||||||
# If set, open dashboard with: /?access_token=<token>
|
# If set, open dashboard with: /?access_token=<token>
|
||||||
POLYWEATHER_DASHBOARD_ACCESS_TOKEN=
|
POLYWEATHER_DASHBOARD_ACCESS_TOKEN=
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
Copy,
|
Copy,
|
||||||
KeyRound,
|
KeyRound,
|
||||||
Loader2,
|
Loader2,
|
||||||
|
LogIn,
|
||||||
LogOut,
|
LogOut,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
@@ -28,6 +29,7 @@ type AuthMeResponse = {
|
|||||||
user_id?: string | null;
|
user_id?: string | null;
|
||||||
email?: string | null;
|
email?: string | null;
|
||||||
entitlement_mode?: string | null;
|
entitlement_mode?: string | null;
|
||||||
|
auth_required?: boolean;
|
||||||
subscription_required?: boolean;
|
subscription_required?: boolean;
|
||||||
subscription_active?: boolean | null;
|
subscription_active?: boolean | null;
|
||||||
};
|
};
|
||||||
@@ -130,6 +132,7 @@ export function AccountCenter() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const userId = backend?.user_id || user?.id || "";
|
const userId = backend?.user_id || user?.id || "";
|
||||||
|
const isAuthenticated = Boolean(userId);
|
||||||
const email = backend?.email || user?.email || "";
|
const email = backend?.email || user?.email || "";
|
||||||
const providerRaw = normalizeProvider(user);
|
const providerRaw = normalizeProvider(user);
|
||||||
const provider = providerRaw ? providerRaw.toUpperCase() : t("account.na");
|
const provider = providerRaw ? providerRaw.toUpperCase() : t("account.na");
|
||||||
@@ -143,6 +146,8 @@ export function AccountCenter() {
|
|||||||
|
|
||||||
const modeLabel = useMemo(() => {
|
const modeLabel = useMemo(() => {
|
||||||
const mode = String(backend?.entitlement_mode || "").trim().toLowerCase();
|
const mode = String(backend?.entitlement_mode || "").trim().toLowerCase();
|
||||||
|
if (mode === "supabase_required") return t("account.mode.supabaseRequired");
|
||||||
|
if (mode === "supabase_optional") return t("account.mode.supabaseOptional");
|
||||||
if (mode === "supabase") return t("account.mode.supabase");
|
if (mode === "supabase") return t("account.mode.supabase");
|
||||||
if (mode === "legacy_token") return t("account.mode.legacy");
|
if (mode === "legacy_token") return t("account.mode.legacy");
|
||||||
if (mode === "disabled") return t("account.mode.disabled");
|
if (mode === "disabled") return t("account.mode.disabled");
|
||||||
@@ -192,10 +197,17 @@ export function AccountCenter() {
|
|||||||
{refreshing ? <Loader2 size={15} className={styles.spin} /> : <RefreshCw size={15} />}
|
{refreshing ? <Loader2 size={15} className={styles.spin} /> : <RefreshCw size={15} />}
|
||||||
{t("account.refresh")}
|
{t("account.refresh")}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" className={styles.primaryBtn} onClick={() => void onSignOut()}>
|
{isAuthenticated ? (
|
||||||
<LogOut size={15} />
|
<button type="button" className={styles.primaryBtn} onClick={() => void onSignOut()}>
|
||||||
{t("account.signOut")}
|
<LogOut size={15} />
|
||||||
</button>
|
{t("account.signOut")}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<Link className={styles.primaryBtn} href="/auth/login?next=%2Faccount">
|
||||||
|
<LogIn size={15} />
|
||||||
|
{t("account.signIn")}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -205,24 +217,33 @@ export function AccountCenter() {
|
|||||||
<h2>{displayName}</h2>
|
<h2>{displayName}</h2>
|
||||||
<p>{email || t("account.na")}</p>
|
<p>{email || t("account.na")}</p>
|
||||||
<div className={styles.badges}>
|
<div className={styles.badges}>
|
||||||
<span className={styles.badge}>
|
{isAuthenticated ? (
|
||||||
<CheckCircle2 size={14} />
|
<>
|
||||||
{t("account.authenticated")}
|
<span className={styles.badge}>
|
||||||
</span>
|
<CheckCircle2 size={14} />
|
||||||
{backend?.subscription_active ? (
|
{t("account.authenticated")}
|
||||||
<span className={styles.badge}>
|
</span>
|
||||||
<ShieldCheck size={14} />
|
{backend?.subscription_active ? (
|
||||||
{t("account.subscriptionActive")}
|
<span className={styles.badge}>
|
||||||
</span>
|
<ShieldCheck size={14} />
|
||||||
) : backend?.subscription_required ? (
|
{t("account.subscriptionActive")}
|
||||||
<span className={styles.badgeWarn}>
|
</span>
|
||||||
<KeyRound size={14} />
|
) : backend?.subscription_required ? (
|
||||||
{t("account.subscriptionRequired")}
|
<span className={styles.badgeWarn}>
|
||||||
</span>
|
<KeyRound size={14} />
|
||||||
|
{t("account.subscriptionRequired")}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className={styles.badgeGhost}>
|
||||||
|
<ShieldCheck size={14} />
|
||||||
|
{t("account.subscriptionUnknown")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span className={styles.badgeGhost}>
|
<span className={styles.badgeGhost}>
|
||||||
<ShieldCheck size={14} />
|
<ShieldCheck size={14} />
|
||||||
{t("account.subscriptionUnknown")}
|
{t("account.guest")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -259,7 +280,9 @@ export function AccountCenter() {
|
|||||||
<dd>
|
<dd>
|
||||||
{backend?.authenticated
|
{backend?.authenticated
|
||||||
? t("account.backend.ok")
|
? t("account.backend.ok")
|
||||||
: t("account.backend.fail")}
|
: backend?.auth_required
|
||||||
|
? t("account.backend.fail")
|
||||||
|
: t("account.guest")}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -328,4 +351,3 @@ export function AccountCenter() {
|
|||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,12 +114,14 @@ const MESSAGES: Record<Locale, Record<string, string>> = {
|
|||||||
"account.subtitle": "查看身份、权限与 Bot 绑定信息",
|
"account.subtitle": "查看身份、权限与 Bot 绑定信息",
|
||||||
"account.backDashboard": "返回看板",
|
"account.backDashboard": "返回看板",
|
||||||
"account.refresh": "刷新",
|
"account.refresh": "刷新",
|
||||||
|
"account.signIn": "登录 / 注册",
|
||||||
"account.signOut": "退出登录",
|
"account.signOut": "退出登录",
|
||||||
"account.loading": "正在同步账户信息...",
|
"account.loading": "正在同步账户信息...",
|
||||||
"account.error": "加载失败: {message}",
|
"account.error": "加载失败: {message}",
|
||||||
"account.updatedAt": "最近同步: {time}",
|
"account.updatedAt": "最近同步: {time}",
|
||||||
"account.guestName": "PolyWeather 用户",
|
"account.guestName": "PolyWeather 用户",
|
||||||
"account.authenticated": "已登录",
|
"account.authenticated": "已登录",
|
||||||
|
"account.guest": "游客模式",
|
||||||
"account.subscriptionActive": "订阅有效",
|
"account.subscriptionActive": "订阅有效",
|
||||||
"account.subscriptionRequired": "需要订阅",
|
"account.subscriptionRequired": "需要订阅",
|
||||||
"account.subscriptionUnknown": "订阅状态未知",
|
"account.subscriptionUnknown": "订阅状态未知",
|
||||||
@@ -138,6 +140,8 @@ const MESSAGES: Record<Locale, Record<string, string>> = {
|
|||||||
"account.field.bindCommand": "绑定命令",
|
"account.field.bindCommand": "绑定命令",
|
||||||
"account.field.bindHint":
|
"account.field.bindHint":
|
||||||
"将下面命令发送到 Telegram Bot,可把网页账户与机器人权限绑定。",
|
"将下面命令发送到 Telegram Bot,可把网页账户与机器人权限绑定。",
|
||||||
|
"account.mode.supabaseRequired": "Supabase 强制登录",
|
||||||
|
"account.mode.supabaseOptional": "Supabase 可选登录",
|
||||||
"account.mode.supabase": "Supabase 会话鉴权",
|
"account.mode.supabase": "Supabase 会话鉴权",
|
||||||
"account.mode.legacy": "Legacy Token 鉴权",
|
"account.mode.legacy": "Legacy Token 鉴权",
|
||||||
"account.mode.disabled": "未启用权限校验",
|
"account.mode.disabled": "未启用权限校验",
|
||||||
@@ -266,12 +270,14 @@ const MESSAGES: Record<Locale, Record<string, string>> = {
|
|||||||
"account.subtitle": "Review identity, access status, and bot binding info",
|
"account.subtitle": "Review identity, access status, and bot binding info",
|
||||||
"account.backDashboard": "Back to Dashboard",
|
"account.backDashboard": "Back to Dashboard",
|
||||||
"account.refresh": "Refresh",
|
"account.refresh": "Refresh",
|
||||||
|
"account.signIn": "Sign in / Sign up",
|
||||||
"account.signOut": "Sign out",
|
"account.signOut": "Sign out",
|
||||||
"account.loading": "Syncing account snapshot...",
|
"account.loading": "Syncing account snapshot...",
|
||||||
"account.error": "Failed to load: {message}",
|
"account.error": "Failed to load: {message}",
|
||||||
"account.updatedAt": "Last synced: {time}",
|
"account.updatedAt": "Last synced: {time}",
|
||||||
"account.guestName": "PolyWeather User",
|
"account.guestName": "PolyWeather User",
|
||||||
"account.authenticated": "Authenticated",
|
"account.authenticated": "Authenticated",
|
||||||
|
"account.guest": "Guest Mode",
|
||||||
"account.subscriptionActive": "Subscription active",
|
"account.subscriptionActive": "Subscription active",
|
||||||
"account.subscriptionRequired": "Subscription required",
|
"account.subscriptionRequired": "Subscription required",
|
||||||
"account.subscriptionUnknown": "Subscription unknown",
|
"account.subscriptionUnknown": "Subscription unknown",
|
||||||
@@ -290,6 +296,8 @@ const MESSAGES: Record<Locale, Record<string, string>> = {
|
|||||||
"account.field.bindCommand": "Binding command",
|
"account.field.bindCommand": "Binding command",
|
||||||
"account.field.bindHint":
|
"account.field.bindHint":
|
||||||
"Send this command to the Telegram bot to bind web account identity.",
|
"Send this command to the Telegram bot to bind web account identity.",
|
||||||
|
"account.mode.supabaseRequired": "Supabase required auth",
|
||||||
|
"account.mode.supabaseOptional": "Supabase optional auth",
|
||||||
"account.mode.supabase": "Supabase session auth",
|
"account.mode.supabase": "Supabase session auth",
|
||||||
"account.mode.legacy": "Legacy token auth",
|
"account.mode.legacy": "Legacy token auth",
|
||||||
"account.mode.disabled": "Auth guard disabled",
|
"account.mode.disabled": "Auth guard disabled",
|
||||||
|
|||||||
+27
-4
@@ -5,10 +5,19 @@ import {
|
|||||||
} from "@/lib/supabase/server";
|
} from "@/lib/supabase/server";
|
||||||
|
|
||||||
const SESSION_COOKIE = "polyweather_entitlement";
|
const SESSION_COOKIE = "polyweather_entitlement";
|
||||||
|
|
||||||
|
function readEnvBool(name: string, fallback: boolean) {
|
||||||
|
const raw = process.env[name];
|
||||||
|
if (raw == null) return fallback;
|
||||||
|
return String(raw).trim().toLowerCase() === "true";
|
||||||
|
}
|
||||||
|
|
||||||
const SUPABASE_AUTH_ENABLED =
|
const SUPABASE_AUTH_ENABLED =
|
||||||
String(process.env.POLYWEATHER_AUTH_ENABLED || "")
|
readEnvBool("POLYWEATHER_AUTH_ENABLED", false);
|
||||||
.trim()
|
const SUPABASE_AUTH_REQUIRED = readEnvBool(
|
||||||
.toLowerCase() === "true";
|
"POLYWEATHER_AUTH_REQUIRED",
|
||||||
|
SUPABASE_AUTH_ENABLED,
|
||||||
|
);
|
||||||
|
|
||||||
function isStaticAsset(pathname: string) {
|
function isStaticAsset(pathname: string) {
|
||||||
return (
|
return (
|
||||||
@@ -114,6 +123,17 @@ async function handleSupabaseAuthGate(request: NextRequest) {
|
|||||||
return NextResponse.redirect(loginUrl);
|
return NextResponse.redirect(loginUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleSupabaseOptionalSession(request: NextRequest) {
|
||||||
|
const response = NextResponse.next({
|
||||||
|
request: {
|
||||||
|
headers: request.headers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const supabase = createSupabaseMiddlewareClient(request, response);
|
||||||
|
await supabase.auth.getUser();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
export async function middleware(request: NextRequest) {
|
export async function middleware(request: NextRequest) {
|
||||||
const { pathname } = request.nextUrl;
|
const { pathname } = request.nextUrl;
|
||||||
if (isStaticAsset(pathname)) {
|
if (isStaticAsset(pathname)) {
|
||||||
@@ -121,7 +141,10 @@ export async function middleware(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (SUPABASE_AUTH_ENABLED && hasSupabaseServerEnv()) {
|
if (SUPABASE_AUTH_ENABLED && hasSupabaseServerEnv()) {
|
||||||
return handleSupabaseAuthGate(request);
|
if (SUPABASE_AUTH_REQUIRED) {
|
||||||
|
return handleSupabaseAuthGate(request);
|
||||||
|
}
|
||||||
|
return handleSupabaseOptionalSession(request);
|
||||||
}
|
}
|
||||||
return handleLegacyTokenGate(request);
|
return handleLegacyTokenGate(request);
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-4
@@ -91,6 +91,10 @@ def _env_bool(name: str, default: bool = False) -> bool:
|
|||||||
_ENTITLEMENT_GUARD_ENABLED = _env_bool("POLYWEATHER_REQUIRE_ENTITLEMENT", False)
|
_ENTITLEMENT_GUARD_ENABLED = _env_bool("POLYWEATHER_REQUIRE_ENTITLEMENT", False)
|
||||||
_ENTITLEMENT_HEADER = "x-polyweather-entitlement"
|
_ENTITLEMENT_HEADER = "x-polyweather-entitlement"
|
||||||
_ENTITLEMENT_TOKEN = (os.getenv("POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN") or "").strip()
|
_ENTITLEMENT_TOKEN = (os.getenv("POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN") or "").strip()
|
||||||
|
_SUPABASE_AUTH_REQUIRED = _env_bool(
|
||||||
|
"POLYWEATHER_AUTH_REQUIRED",
|
||||||
|
SUPABASE_ENTITLEMENT.enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _legacy_service_token_valid(request: Request) -> bool:
|
def _legacy_service_token_valid(request: Request) -> bool:
|
||||||
@@ -100,10 +104,26 @@ def _legacy_service_token_valid(request: Request) -> bool:
|
|||||||
return bool(_ENTITLEMENT_TOKEN and token == _ENTITLEMENT_TOKEN)
|
return bool(_ENTITLEMENT_TOKEN and token == _ENTITLEMENT_TOKEN)
|
||||||
|
|
||||||
|
|
||||||
|
def _bind_optional_supabase_identity(request: Request) -> None:
|
||||||
|
if not SUPABASE_ENTITLEMENT.configured:
|
||||||
|
return
|
||||||
|
access_token = extract_bearer_token(request.headers.get("authorization"))
|
||||||
|
if not access_token:
|
||||||
|
return
|
||||||
|
identity = SUPABASE_ENTITLEMENT.get_identity(access_token)
|
||||||
|
if not identity:
|
||||||
|
return
|
||||||
|
request.state.auth_user_id = identity.user_id
|
||||||
|
request.state.auth_email = identity.email
|
||||||
|
|
||||||
|
|
||||||
def _assert_entitlement(request: Request) -> None:
|
def _assert_entitlement(request: Request) -> None:
|
||||||
if SUPABASE_ENTITLEMENT.enabled:
|
if SUPABASE_ENTITLEMENT.enabled:
|
||||||
if _legacy_service_token_valid(request):
|
if _legacy_service_token_valid(request):
|
||||||
return
|
return
|
||||||
|
if not _SUPABASE_AUTH_REQUIRED:
|
||||||
|
_bind_optional_supabase_identity(request)
|
||||||
|
return
|
||||||
if not SUPABASE_ENTITLEMENT.configured:
|
if not SUPABASE_ENTITLEMENT.configured:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=503,
|
status_code=503,
|
||||||
@@ -1003,28 +1023,36 @@ async def city_history(request: Request, name: str):
|
|||||||
@app.get("/api/auth/me")
|
@app.get("/api/auth/me")
|
||||||
async def auth_me(request: Request):
|
async def auth_me(request: Request):
|
||||||
_assert_entitlement(request)
|
_assert_entitlement(request)
|
||||||
|
_bind_optional_supabase_identity(request)
|
||||||
|
|
||||||
user_id = getattr(request.state, "auth_user_id", None)
|
user_id = getattr(request.state, "auth_user_id", None)
|
||||||
subscription_required = bool(
|
subscription_required = bool(
|
||||||
SUPABASE_ENTITLEMENT.enabled and SUPABASE_ENTITLEMENT.require_subscription
|
SUPABASE_ENTITLEMENT.enabled
|
||||||
|
and _SUPABASE_AUTH_REQUIRED
|
||||||
|
and SUPABASE_ENTITLEMENT.require_subscription
|
||||||
)
|
)
|
||||||
subscription_active = None
|
subscription_active = None
|
||||||
if SUPABASE_ENTITLEMENT.enabled and user_id:
|
if SUPABASE_ENTITLEMENT.enabled and user_id:
|
||||||
try:
|
try:
|
||||||
subscription_active = SUPABASE_ENTITLEMENT.has_active_subscription(user_id)
|
if SUPABASE_ENTITLEMENT.require_subscription:
|
||||||
|
subscription_active = SUPABASE_ENTITLEMENT.has_active_subscription(user_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
subscription_active = None
|
subscription_active = None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"authenticated": True,
|
"authenticated": bool(user_id),
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"email": getattr(request.state, "auth_email", None),
|
"email": getattr(request.state, "auth_email", None),
|
||||||
"entitlement_mode": (
|
"entitlement_mode": (
|
||||||
"supabase"
|
"supabase_required"
|
||||||
|
if SUPABASE_ENTITLEMENT.enabled and _SUPABASE_AUTH_REQUIRED
|
||||||
|
else "supabase_optional"
|
||||||
if SUPABASE_ENTITLEMENT.enabled
|
if SUPABASE_ENTITLEMENT.enabled
|
||||||
else "legacy_token"
|
else "legacy_token"
|
||||||
if _ENTITLEMENT_GUARD_ENABLED
|
if _ENTITLEMENT_GUARD_ENABLED
|
||||||
else "disabled"
|
else "disabled"
|
||||||
),
|
),
|
||||||
|
"auth_required": bool(SUPABASE_ENTITLEMENT.enabled and _SUPABASE_AUTH_REQUIRED),
|
||||||
"subscription_required": subscription_required,
|
"subscription_required": subscription_required,
|
||||||
"subscription_active": subscription_active,
|
"subscription_active": subscription_active,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user