using Inversion; using Json; namespace Spry.Authorisation { /** * Service for generating and validating authorisation tokens. * * Token Format: * - JSON payload containing identity data and metadata * - Signed with Ed25519 (server signing key) * - Encrypted with X25519 (server sealing key) * - Base64url encoded * * This service uses the same encryption approach as SessionService. */ public class AuthorisationTokenService : GLib.Object { private CryptographyProvider _crypto = inject(); // Configuration private TimeSpan _token_duration = TimeSpan.HOUR * 24; /** * Default token validity duration. */ public TimeSpan token_duration { get { return _token_duration; } set { _token_duration = value; } } /** * Creates a new AuthorisationTokenService with default configuration. */ public AuthorisationTokenService() { // Default configuration } // ========================================================================= // Token Generation // ========================================================================= /** * Generates a signed and encrypted authorisation token for an identity. * * @param identity The identity to create a token for * @param expires_at Optional custom expiry (defaults to token_duration from now) * @return The encrypted token string */ public string generate_token(Identity identity, DateTime? expires_at = null) { // Calculate expiry DateTime token_expiry; if (expires_at != null) { token_expiry = (!)expires_at; } else { token_expiry = new DateTime.now_utc().add(_token_duration); } // Create token from identity var duration = token_expiry.difference(new DateTime.now_utc()); var token = new AuthorisationToken.from_identity(identity, duration); // Serialize to JSON var json_obj = token.to_json(); var node = new Json.Node(Json.NodeType.OBJECT); node.set_object(json_obj); var json_str = Json.to_string(node, false); // Sign and seal using CryptographyProvider return _crypto.sign_then_seal_token(json_str, token_expiry); } /** * Generates a token from an existing AuthorisationToken. * * @param token The token to serialize and encrypt * @return The encrypted token string */ public string generate_token_from_token(AuthorisationToken token) { var json_str = token.to_json_string(); return _crypto.sign_then_seal_token(json_str, token.expires_at); } // ========================================================================= // Token Validation // ========================================================================= /** * Validates a token string and returns the parsed token. * * This method: * - Uses CryptographyProvider.unseal_then_verify_token() * - Checks expiry * - Parses the JSON payload * * @param token_string The encrypted token string * @return The AuthorisationToken, or null if invalid/expired */ public AuthorisationToken? parse_token(string token_string) { try { // Decrypt and verify the token var result = _crypto.unseal_then_verify_token(token_string); if (!result.is_valid) { return null; } // Check if token is expired if (result.is_expired) { return null; } // Get the payload var payload = result.payload; if (payload == null) { return null; } // Parse the JSON var token = AuthorisationToken.from_json_string((!)payload); if (token == null) { return null; } // Double-check expiry from the token itself if (token.is_expired()) { return null; } return token; } catch (Error e) { return null; } } /** * Validates a token and returns detailed validation result. * * @param token_string The encrypted token string * @return A TokenValidationResult with status and token data */ public TokenValidationResult validate_token(string token_string) { // Decrypt and verify the token var crypto_result = _crypto.unseal_then_verify_token(token_string); if (!crypto_result.is_valid) { return new TokenValidationResult.failure( crypto_result.error_message ?? "Invalid token" ); } if (crypto_result.is_expired) { return new TokenValidationResult.failure("Token has expired", true); } var payload = crypto_result.payload; if (payload == null) { return new TokenValidationResult.failure("Empty token payload"); } var token = AuthorisationToken.from_json_string((!)payload); if (token == null) { return new TokenValidationResult.failure("Failed to parse token payload"); } // Double-check expiry from the token itself if (token.is_expired()) { return new TokenValidationResult.failure("Token has expired", true); } return new TokenValidationResult.success(token); } } /** * Result of token validation containing the token and status information. */ public class TokenValidationResult : GLib.Object { /** * Whether the token was successfully validated. */ public bool is_valid { get; set; } /** * The parsed token, or null if validation failed. */ public AuthorisationToken? token { get; set; } /** * Error message describing why validation failed. */ public string? error_message { get; set; } /** * Whether the token has expired. */ public bool is_expired { get; set; } /** * Creates a successful validation result. */ public TokenValidationResult.success(AuthorisationToken token) { GLib.Object( is_valid: true, token: token, error_message: null, is_expired: false ); } /** * Creates a failed validation result. */ public TokenValidationResult.failure(string error_message, bool expired = false) { GLib.Object( is_valid: false, token: null, error_message: error_message, is_expired: expired ); } } }