This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/breakpilot-drive/UnityScripts/Core/AuthManager.cs
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

248 lines
7.1 KiB
C#

// ==============================================
// AuthManager.cs - Unity Auth Handler
// ==============================================
// Handles JWT token for Keycloak authentication.
// Supports token via URL parameter or PostMessage from parent frame.
using System;
using System.Runtime.InteropServices;
using UnityEngine;
namespace BreakpilotDrive
{
public class AuthManager : MonoBehaviour
{
public static AuthManager Instance { get; private set; }
[Header("Auth Configuration")]
[SerializeField] private bool requireAuth = false;
[SerializeField] private string devUserId = "dev-user-123";
// Current auth state
private string jwtToken;
private string userId;
private bool isAuthenticated;
// Events
public event Action<string> OnAuthenticated;
public event Action<string> OnAuthFailed;
// JavaScript interface for WebGL
#if UNITY_WEBGL && !UNITY_EDITOR
[DllImport("__Internal")]
private static extern string GetTokenFromURL();
[DllImport("__Internal")]
private static extern string GetTokenFromParent();
[DllImport("__Internal")]
private static extern void RequestTokenFromParent();
#endif
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
void Start()
{
InitializeAuth();
}
// ==============================================
// Initialization
// ==============================================
private void InitializeAuth()
{
// Development mode - use dev user
if (!requireAuth || Application.isEditor)
{
SetDevUser();
return;
}
#if UNITY_WEBGL && !UNITY_EDITOR
// Try URL parameter first
string urlToken = GetTokenFromURL();
if (!string.IsNullOrEmpty(urlToken))
{
SetToken(urlToken);
return;
}
// Try parent frame (iframe scenario)
RequestTokenFromParent();
#else
// Non-WebGL builds use dev user
SetDevUser();
#endif
}
private void SetDevUser()
{
userId = devUserId;
isAuthenticated = true;
jwtToken = null;
Debug.Log($"[Auth] Development mode - User: {userId}");
OnAuthenticated?.Invoke(userId);
}
// ==============================================
// Token Management
// ==============================================
public void SetToken(string token)
{
if (string.IsNullOrEmpty(token))
{
OnAuthFailed?.Invoke("Empty token");
return;
}
jwtToken = token;
userId = ExtractUserIdFromToken(token);
if (!string.IsNullOrEmpty(userId))
{
isAuthenticated = true;
Debug.Log($"[Auth] Authenticated - User: {userId}");
OnAuthenticated?.Invoke(userId);
}
else
{
OnAuthFailed?.Invoke("Could not extract user ID from token");
}
}
// Called from JavaScript via SendMessage
public void ReceiveTokenFromJS(string token)
{
Debug.Log("[Auth] Received token from JavaScript");
SetToken(token);
}
// Called from JavaScript if auth fails
public void AuthFailedFromJS(string error)
{
Debug.LogWarning($"[Auth] JavaScript auth failed: {error}");
// Fall back to dev user in development
if (!requireAuth)
{
SetDevUser();
}
else
{
OnAuthFailed?.Invoke(error);
}
}
private string ExtractUserIdFromToken(string token)
{
try
{
// JWT has 3 parts: header.payload.signature
string[] parts = token.Split('.');
if (parts.Length != 3) return null;
// Decode payload (base64)
string payload = parts[1];
// Fix base64 padding
int padding = 4 - (payload.Length % 4);
if (padding < 4) payload += new string('=', padding);
// Replace URL-safe chars
payload = payload.Replace('-', '+').Replace('_', '/');
byte[] bytes = Convert.FromBase64String(payload);
string json = System.Text.Encoding.UTF8.GetString(bytes);
// Simple JSON parsing for "sub" claim
var claims = JsonUtility.FromJson<JWTPayload>(json);
return claims?.sub;
}
catch (Exception e)
{
Debug.LogWarning($"[Auth] Token parsing failed: {e.Message}");
return null;
}
}
[Serializable]
private class JWTPayload
{
public string sub;
public string email;
public string name;
public long exp;
public long iat;
}
// ==============================================
// Public API
// ==============================================
public bool IsAuthenticated => isAuthenticated;
public string UserId => userId;
public string Token => jwtToken;
public bool RequiresAuth => requireAuth;
public string GetAuthHeader()
{
if (string.IsNullOrEmpty(jwtToken))
return null;
return $"Bearer {jwtToken}";
}
public void Logout()
{
jwtToken = null;
userId = null;
isAuthenticated = false;
Debug.Log("[Auth] Logged out");
}
// Check if token is expired
public bool IsTokenExpired()
{
if (string.IsNullOrEmpty(jwtToken))
return true;
try
{
string[] parts = jwtToken.Split('.');
if (parts.Length != 3) return true;
string payload = parts[1];
int padding = 4 - (payload.Length % 4);
if (padding < 4) payload += new string('=', padding);
payload = payload.Replace('-', '+').Replace('_', '/');
byte[] bytes = Convert.FromBase64String(payload);
string json = System.Text.Encoding.UTF8.GetString(bytes);
var claims = JsonUtility.FromJson<JWTPayload>(json);
if (claims?.exp > 0)
{
var expTime = DateTimeOffset.FromUnixTimeSeconds(claims.exp).UtcDateTime;
return DateTime.UtcNow > expTime;
}
}
catch { }
return false;
}
}
}