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>
248 lines
7.1 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|