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>
This commit is contained in:
247
breakpilot-drive/UnityScripts/Core/AuthManager.cs
Normal file
247
breakpilot-drive/UnityScripts/Core/AuthManager.cs
Normal file
@@ -0,0 +1,247 @@
|
||||
// ==============================================
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user