| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- 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<CryptographyProvider>();
- // 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
- );
- }
- }
- }
|