-module(ywt). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/ywt.gleam"). -export([generate_key/1, sign_bits/2, sign_string/2, verify_bits/3, verify_string/3, decode_unsafely_without_validation/2, decode/4, encode/3]). -export_type([parse_error/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type parse_error() :: malformed_token | invalid_header_encoding | invalid_payload_encoding | invalid_signature_encoding | {invalid_header_json, gleam@json:decode_error()} | {invalid_payload_json, gleam@json:decode_error()} | no_matching_key | invalid_signature | {token_expired, gleam@time@timestamp:timestamp()} | {token_not_yet_valid, gleam@time@timestamp:timestamp()} | {invalid_issuer, list(binary()), binary()} | {invalid_audience, list(binary()), binary()} | {invalid_subject, list(binary()), binary()} | {invalid_id, list(binary()), binary()} | {missing_claim, binary()} | {claim_decoding_error, binary(), list(gleam@dynamic@decode:decode_error())} | {invalid_custom_claim, binary()} | {payload_decoding_error, list(gleam@dynamic@decode:decode_error())}. -file("src/ywt.gleam", 110). -spec from_core_error(ywt@internal@core:parse_error()) -> parse_error(). from_core_error(Error) -> case Error of {claim_decoding_error, Claim_name, Error@1} -> {claim_decoding_error, Claim_name, Error@1}; {invalid_audience, Expected, Actual} -> {invalid_audience, Expected, Actual}; {invalid_custom_claim, Claim_name@1} -> {invalid_custom_claim, Claim_name@1}; invalid_header_encoding -> invalid_header_encoding; {invalid_header_json, Error@2} -> {invalid_header_json, Error@2}; {invalid_id, Expected@1, Actual@1} -> {invalid_id, Expected@1, Actual@1}; {invalid_issuer, Expected@2, Actual@2} -> {invalid_issuer, Expected@2, Actual@2}; invalid_payload_encoding -> invalid_payload_encoding; {invalid_payload_json, Error@3} -> {invalid_payload_json, Error@3}; invalid_signature -> invalid_signature; invalid_signature_encoding -> invalid_signature_encoding; {invalid_subject, Expected@3, Actual@3} -> {invalid_subject, Expected@3, Actual@3}; malformed_token -> malformed_token; {missing_claim, Claim_name@2} -> {missing_claim, Claim_name@2}; no_matching_key -> no_matching_key; {payload_decoding_error, Error@4} -> {payload_decoding_error, Error@4}; {token_expired, Expired_at} -> {token_expired, Expired_at}; {token_not_yet_valid, Not_before} -> {token_not_yet_valid, Not_before} end. -file("src/ywt.gleam", 160). ?DOC( " Generates a new cryptographically secure signing key for the specified algorithm.\n" "\n" " This function creates fresh signing keys with appropriate parameters for each\n" " algorithm type. All keys are generated using cryptographically secure random\n" " number generators and follow current security best practices.\n" "\n" " The generated keys will have a random id set that can be used to identify the key\n" " during key rotation.\n" "\n" " ## Usage\n" " Use this for key generation in development, testing, or when implementing\n" " key rotation systems. For production use, consider generating keys offline\n" " and storing them in secure key management systems.\n" "\n" " ## Key Parameters\n" " - **HMAC**: Full-entropy random secrets of appropriate length (at least 32/48/64 bytes)\n" " - **RSA**: 4096-bit modulus with secure random prime generation\n" " - **ECDSA**: Secure random private scalars on specified curves. The digest type matches the selected curve.\n" "\n" "
\n" " \n" " Back to top ↑\n" " \n" "
\n" ). -spec generate_key(ywt@algorithm:algorithm()) -> ywt@sign_key:sign_key(). generate_key(Algorithm) -> Key = ywt@algorithm:generate_key( Algorithm, fun ywt_ffi:generate_hmac/1, fun ywt_ffi:generate_ecdsa/2, fun ywt_ffi:generate_rsa/3 ), ywt@sign_key:with_random_id(Key). -file("src/ywt.gleam", 215). ?DOC( " Creates a cryptographic signature for the given message using the specified signing key.\n" "\n" " This is a low-level function that performs the actual cryptographic signing operation.\n" "\n" " ## Usage\n" " This function is primarily used internally by higher-level JWT functions. Use this\n" " directly only when you need raw signature operations outside of JWT context.\n" "\n" " ## Security Considerations\n" " - Never sign untrusted or unvalidated input data\n" " - Ensure signing keys are stored securely and accessed only by authorized code\n" "\n" " ## Example\n" " ```gleam\n" " let message = <<\"Hello, world!\":utf8>>\n" " let signing_key = load_secure_signing_key()\n" "\n" " let signature = ywt.sign_bits(message, signing_key)\n" " // signature is raw bytes that can be verified with corresponding verify key\n" " ```\n" "\n" " ⚠️ **Warning:** This function performs raw cryptographic operations. Most applications\n" " should use the higher-level JWT functions instead.\n" "\n" "
\n" " \n" " Back to top ↑\n" " \n" "
\n" ). -spec sign_bits(bitstring(), ywt@sign_key:sign_key()) -> bitstring(). sign_bits(Message, Key) -> ywt_ffi:sign(Message, Key). -file("src/ywt.gleam", 248). ?DOC( " Creates a base64url-encoded signature for a UTF-8 string message.\n" "\n" " This is a convenience wrapper around `sign_bits` that handles string encoding\n" " and base64url encoding of the signature, commonly used for signing JWT components.\n" "\n" " ## Usage\n" " Use this when you need to sign string data and want the signature in base64url\n" " format for use in web contexts like JWTs or HTTP headers.\n" "\n" " ## Security Considerations\n" " - Same security considerations as `sign_bits` apply\n" " - The string is encoded as UTF-8 before signing\n" " - Verify signatures using the corresponding `verify_string` function\n" "\n" " ## Example\n" " ```gleam\n" " let jwt_payload = \"eyJzdWIiOiIxMjM0NTY3ODkwIn0\"\n" " let signing_key = load_jwt_signing_key()\n" "\n" " let signature = sign_string(jwt_payload, signing_key))\n" " // signature is base64url-encoded string ready for JWT use\n" " ```\n" "\n" " 💡 **Note:** The returned signature is base64url-encoded (URL-safe, no padding)\n" " as required by JWT and other web standards.\n" "\n" "
\n" " \n" " Back to top ↑\n" " \n" "
\n" ). -spec sign_string(binary(), ywt@sign_key:sign_key()) -> binary(). sign_string(Message, Key) -> Bits = ywt_ffi:sign(<>, Key), gleam@bit_array:base64_url_encode(Bits, false). -file("src/ywt.gleam", 284). ?DOC( " Verifies a cryptographic signature against a message using the specified verification key.\n" "\n" " This is a low-level function that performs the actual cryptographic verification.\n" " Returns `True` if the signature is valid for the given message and key, `False` otherwise.\n" "\n" " ## Usage\n" " Use this for raw signature verification operations. The verification algorithm\n" " is determined by the key type and must match the algorithm used for signing.\n" "\n" " ## Example\n" " ```gleam\n" " let message = <<\"Hello, world!\":utf8>>\n" " let signature = // ... received signature bytes\n" " let verify_key = load_verification_key()\n" "\n" " let is_valid = verify_bits(message, signature, verify_key)\n" " case is_valid {\n" " True -> process_verified_message(message)\n" " False -> reject_invalid_signature()\n" " }\n" " ```\n" "\n" " 🔒 **Critical:** Never trust data with invalid signatures. A `False` result\n" " indicates potential tampering or use of wrong keys.\n" "\n" "
\n" " \n" " Back to top ↑\n" " \n" "
\n" ). -spec verify_bits(bitstring(), bitstring(), ywt@verify_key:verify_key()) -> boolean(). verify_bits(Message, Signature, Key) -> ywt_ffi:verify(Message, Signature, Key). -file("src/ywt.gleam", 330). ?DOC( " Verifies a base64url-encoded signature against a UTF-8 string message.\n" "\n" " This is a convenience wrapper around `verify_bits` that handles string encoding\n" " and base64url decoding of the signature. Commonly used for verifying JWT signatures.\n" "\n" " ## Usage\n" " Use this when verifying signatures that are base64url-encoded, such as those\n" " from JWTs or other web-based cryptographic protocols.\n" "\n" " ## Security Considerations\n" " - Same security considerations as `verify_bits` apply\n" " - Invalid base64url encoding in the signature automatically returns `False`\n" "\n" " ## Example\n" " ```gleam\n" " let payload = \"eyJzdWIiOiIxMjM0NTY3ODkwIn0\"\n" " let signature = \"base64url-encoded-signature-string\"\n" " let verify_key = load_jwt_verification_key()\n" "\n" " let is_valid = verify_string(payload, signature, verify_key)\n" " case is_valid {\n" " True -> {\n" " // Signature is valid, payload can be trusted\n" " process_authenticated_request(payload)\n" " }\n" " False -> {\n" " // Signature invalid - reject the request\n" " return_authentication_error()\n" " }\n" " }\n" " ```\n" "\n" " ⚠️ **Important:** Malformed base64url signatures return `False` rather than\n" " causing errors, ensuring consistent handling of invalid input.\n" "\n" "
\n" " \n" " Back to top ↑\n" " \n" "
\n" ). -spec verify_string(binary(), binary(), ywt@verify_key:verify_key()) -> boolean(). verify_string(Message, Signature, Key) -> case gleam@bit_array:base64_url_decode(Signature) of {ok, Signature@1} -> ywt_ffi:verify(<>, Signature@1, Key); {error, _} -> false end. -file("src/ywt.gleam", 354). ?DOC( " Extracts payload from a JWT without verifying its signature or claims.\n" "\n" " 🚨 **USE WITH EXTREME CAUTION** Only use this when you need to inspect tokens\n" " from trusted sources where signature verification is handled elsewhere.\n" "\n" " ## Security Considerations\n" " - **NEVER use this in production for authentication**\n" " - Tokens could be forged or tampered with\n" " - No expiration or claim validation is performed\n" " - Only use when signature validation happens at a different layer\n" " - Consider this as dangerous as accepting any user input\n" "\n" "
\n" " \n" " Back to top ↑\n" " \n" "
\n" ). -spec decode_unsafely_without_validation( binary(), gleam@dynamic@decode:decoder(GGT) ) -> {ok, GGT} | {error, nil}. decode_unsafely_without_validation(Jwt, Payload_decoder) -> ywt@internal@jwt:decode_unsafely_without_validation(Jwt, Payload_decoder). -file("src/ywt.gleam", 401). ?DOC( " Verifies a JWT signature and validates all claims, returning the decoded payload if successful.\n" "\n" " Use this to validate incoming JWTs from clients, ensuring they're authentic and\n" " haven't been tampered with.\n" "\n" " ## Security Considerations\n" " - Implement appropriate claims validation (expiration, issuer, audience)\n" " - Handle verification failures securely (don't leak information)\n" " - Consider rate limiting to prevent brute force attacks\n" "\n" " ## Example\n" " ```gleam\n" " // Define expected claims for validation\n" " let claims = [\n" " claim.expires_at(max_age: duration.hours(1), leeway: duration.minutes(5)),\n" " claim.issuer(\"my-app\", []),\n" " ]\n" "\n" " // Parse and validate the JWT\n" " case ywt.decode(jwt_token, using: payload_decoder, claims:, keys: [verify_key]) {\n" " Ok(payload) -> {\n" " // JWT is valid, use the payload\n" " let user_id = // extract user ID from payload\n" " authorize_request(user_id)\n" " }\n" " Error(_) -> {\n" " // JWT is invalid - reject the request\n" " unauthorized_response()\n" " }\n" " }\n" " ```\n" "\n" " 💡 **Best Practice:** Always validate JWTs on every request and never trust\n" " client-provided tokens without verification.\n" "\n" "
\n" " \n" " Back to top ↑\n" " \n" "
\n" ). -spec decode( binary(), gleam@dynamic@decode:decoder(GGX), list(ywt@claim:claim()), list(ywt@verify_key:verify_key()) ) -> {ok, GGX} | {error, parse_error()}. decode(Jwt, Decoder, Claims, Keys) -> Verify = fun(Message, Signature, Key, Next) -> Next(ywt_ffi:verify(Message, Signature, Key)) end, Resolve = fun(_capture) -> gleam@result:map_error(_capture, fun from_core_error/1) end, ywt@internal@jwt:decode(Jwt, Decoder, Claims, Keys, Verify, Resolve). -file("src/ywt.gleam", 456). ?DOC( " Creates a signed JWT containing the specified payload and claims.\n" "\n" " Signed JWTs prevent actors without access to the SignKey from modifying it.\n" " This can be verified using the corresponding VerifyKey.\n" "\n" " ## Security Considerations\n" " - JWTs are signed, not encrypted - all data is publicly readable\n" " - Never include sensitive data like passwords or personal information\n" " - Keep payloads small to avoid large tokens\n" " - Include appropriate expiration times in your claims\n" " - Use strong signing keys and rotate them regularly\n" "\n" " If a field is present in claims as well as the payload, the payload takes\n" " precedence.\n" "\n" " ## Example\n" " ```gleam\n" " // Create a user session token\n" " let payload = [\n" " #(\"sub\", json.string(\"user_12345\")),\n" " #(\"role\", json.string(\"developer\")),\n" " #(\"permissions\", json.array([json.string(\"read\"), json.string(\"write\")]))\n" " ]\n" "\n" " let claims = [\n" " claim.issued_at(),\n" " claim.expires_at(max_age: duration.hours(1), leeway: duration.minutes(5)),\n" " claim.issuer(\"my-app\", []),\n" " ]\n" "\n" " ywt.encode(payload: payload, claims: claims, key: sign_key)\n" " // Returns: \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ...\"\n" " ```\n" "\n" " ⚠️ **Remember:** Anyone can decode and read JWT contents. Only include data\n" " you're comfortable being public.\n" "\n" "
\n" " \n" " Back to top ↑\n" " \n" "
\n" ). -spec encode( list({binary(), gleam@json:json()}), list(ywt@claim:claim()), ywt@sign_key:sign_key() ) -> binary(). encode(Payload, Claims, Key) -> Sign = fun(Message, Key@1, Next) -> Next(ywt_ffi:sign(Message, Key@1)) end, ywt@internal@jwt:encode(Payload, Claims, Key, Sign).