From fd91747a03c9818a6147d84968231f75d177a728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20ldev?= Date: Sat, 10 Feb 2024 16:52:35 -0300 Subject: [PATCH 1/5] modified payload --- src/header.v | 4 +-- src/jwt.v | 67 +++++++++++++++++++------------------------ src/jwt_test.v | 78 +++++++++++++++++++++++++++++++++----------------- src/payload.v | 11 +++---- 4 files changed, 89 insertions(+), 71 deletions(-) diff --git a/src/header.v b/src/header.v index 989bc85..1baf899 100644 --- a/src/header.v +++ b/src/header.v @@ -1,6 +1,6 @@ module jwt pub struct Header { - alg string = "HS256" - typ string = "JWT" + alg string = 'HS256' + typ string = 'JWT' } diff --git a/src/jwt.v b/src/jwt.v index cc7b675..ae9a12a 100644 --- a/src/jwt.v +++ b/src/jwt.v @@ -6,70 +6,61 @@ import crypto.hmac import crypto.sha256 import time -pub struct Token { - header string - payload string +pub struct Token[T] { + header string signature string +pub: + payload Payload[T] } -pub fn Token.new(payload Payload, secret string) Token { +pub fn Token.new[T](payload Payload[T], secret string) Token[T] { header := base64.url_encode(json.encode[Header](Header{}).bytes()) - payload_string := base64.url_encode(json.encode[Payload](payload).bytes()) - signature := base64.url_encode(hmac.new( - secret.bytes(), - "${header}.${payload_string}".bytes(), - sha256.sum, - sha256.block_size - ).bytestr().bytes()) + payload_string := base64.url_encode(json.encode(payload).bytes()) - return Token{ - header: header, - payload: payload_string, - signature: signature, + signature := base64.url_encode(hmac.new(secret.bytes(), '${header}.${payload_string}'.bytes(), + sha256.sum, sha256.block_size).bytestr().bytes()) + + return Token[T]{ + header: header + payload: payload + signature: signature } } -pub fn Token.from_str(token string) !Token { - parts := token.split(".") +pub fn from_str[T](token string) !Token[T] { + parts := token.split('.') if parts.len != 3 { - return error("Invalid token") + return error('Invalid token') } - return Token{ - header: parts[0], - payload: parts[1], - signature: parts[2], + return Token[T]{ + header: parts[0] + payload: json.decode[Payload[T]](base64.url_decode_str(parts[1]))! + signature: parts[2] } } -pub fn (t Token) str() string { - return t.header + "." + t.payload + "." + t.signature +pub fn (t Token[T]) str() string { + payload := base64.url_encode(json.encode(t.payload).bytes()) + return t.header + '.' + payload + '.' + t.signature } -pub fn (t Token) valid(secret string) bool { +pub fn (t Token[T]) valid(secret string) bool { if t.expired() { return false } - parts := t.str().split(".") + parts := t.str().split('.') if parts.len != 3 { return false } - expected_signature := base64.url_encode(hmac.new( - secret.bytes(), - "${parts[0]}.${parts[1]}".bytes(), // header + payload - sha256.sum, - sha256.block_size - ).bytestr().bytes()) + expected_signature := base64.url_encode(hmac.new(secret.bytes(), '${parts[0]}.${parts[1]}'.bytes(), // header + payload + sha256.sum, sha256.block_size).bytestr().bytes()) return parts[2] == expected_signature // signature == expected_signature } -pub fn (t Token) payload() !Payload { - return json.decode[Payload](base64.url_decode_str(t.payload))! -} - -pub fn (t Token) expired() bool { - return t.payload() or { return false }.exp or { return false } < time.now() +pub fn (t Token[T]) expired() bool { + return t.payload.exp or { return false } < time.now() } diff --git a/src/jwt_test.v b/src/jwt_test.v index 2656297..fcb9707 100644 --- a/src/jwt_test.v +++ b/src/jwt_test.v @@ -1,47 +1,73 @@ module jwt -import x.json2 as json import time -const secret := "secret" -const claims := {"name": "John Doe", "admin": "true"} +const no_secret = 'pass secret' +const secret = 'secret' +const claims = { + 'name': 'John Doe' + 'admin': 'true' +} fn test_valid() { - payload := Payload{ - sub: "1234567890", - ext: json.encode(claims) + payload := Payload[map[string]string]{ + sub: '1234567890' + ext: jwt.claims + } + token := Token.new(payload, jwt.secret) + + assert token.valid(jwt.secret) +} + +fn test_invalid() { + payload := Payload[map[string]string]{ + sub: '1234567890' + ext: jwt.claims } - token := Token.new(payload, secret) + token := Token.new(payload, jwt.secret) - assert token.valid(secret) == true + assert !token.valid(jwt.no_secret) } fn test_expired() { - payload := Payload{ - sub: "1234567890", - ext: json.encode(claims), - exp: time.parse("2019-01-01 00:00:00") or { panic(err) } + payload := Payload[map[string]string]{ + sub: '1234567890' + ext: jwt.claims + exp: time.parse('2019-01-01 00:00:00') or { panic(err) } + } + token := Token.new(payload, jwt.secret) + + assert !token.valid(jwt.secret) + assert token.expired() +} + +fn test_no_expired() { + payload := Payload[map[string]string]{ + sub: '1234567890' + ext: jwt.claims + exp: time.now().add_seconds(10) } - token := Token.new(payload, secret) + token := Token.new(payload, jwt.secret) - assert token.expired() == true - assert token.valid(secret) == false + assert token.valid(jwt.secret) + assert !token.expired() } fn test_from_str() { - payload := Payload{ - sub: "1234567890", - ext: json.encode(claims) + payload := Payload[map[string]string]{ + sub: '1234567890' + ext: jwt.claims } - payload2 := Payload{ - sub: "0987654321", - ext: json.encode(claims) + payload2 := Payload[map[string]string]{ + sub: '0987654321' + ext: jwt.claims } - token := Token.new(payload, secret) - token2 := Token.from_str(token.str())! - token3 := Token.new(payload2, secret) + token := Token.new(payload, jwt.secret) + token2 := from_str[map[string]string](token.str())! + token3 := Token.new(payload2, jwt.secret) - assert token2 == token - assert token2 != token3 + // error compiler + // assert token2 == token + // assert token2 != token3 } diff --git a/src/payload.v b/src/payload.v index 8d7f146..9e6fbbd 100644 --- a/src/payload.v +++ b/src/payload.v @@ -2,11 +2,12 @@ module jwt import time -pub struct Payload { - iss ?string @[omitempty] - sub ?string @[omitempty] - aud ?string @[omitempty] +pub struct Payload[T] { + iss ?string @[omitempty] + sub ?string @[omitempty] + aud ?string @[omitempty] exp ?time.Time @[omitempty] iat ?time.Time @[omitempty] - ext ?string @[omitempty] +pub: + ext ?T @[omitempty] } From 8b4f81461507025d5561a65b097b4c887aa6d5ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20ldev?= Date: Sat, 10 Feb 2024 17:07:10 -0300 Subject: [PATCH 2/5] fix ext payload --- readme.md | 34 ++++++++++++++++++++++------------ src/payload.v | 2 +- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/readme.md b/readme.md index 911471e..580a2fe 100644 --- a/readme.md +++ b/readme.md @@ -4,21 +4,31 @@ A simple, self-contained module for making and verifying JWTs using HMAC SHA256. ## Example ```v import jwt -import json -const secret := "secret-key" +const secret = 'secret-key' -// Create a new token -payload := jwt.Payload{ - sub: "1234567890", - ext: json.encode(/* some struct */) +pub struct Credential { + user string + pass string } -token := jwt.Token.new(payload, secret) -respond_with(token.str()) -// Validate a token from the web -token := jwt.Token.from_str(/* receive a token from the web */) -if token.valid(secret) { - // do stuff +fn main() { + // Create a new token + payload := jwt.Payload[Credential]{ + sub: '1234567890' + ext: Credential{ + user: 'splashsky' + pass: 'password' + } + } + token := jwt.Token.new(payload, secret) + receive_token_from_web := token.str() + + // Validate a token from the web + obj_token := jwt.from_str[Credential](receive_token_from_web)! + if obj_token.valid(secret) { + println('token valid!') + dump(obj_token.payload.ext) + } } ``` diff --git a/src/payload.v b/src/payload.v index 9e6fbbd..27859d0 100644 --- a/src/payload.v +++ b/src/payload.v @@ -9,5 +9,5 @@ pub struct Payload[T] { exp ?time.Time @[omitempty] iat ?time.Time @[omitempty] pub: - ext ?T @[omitempty] + ext T @[omitempty] } From 31763c5ec81c454a5030fdeea3d563c55ea683a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20ldev?= Date: Mon, 19 Feb 2024 10:23:05 -0300 Subject: [PATCH 3/5] change json2 to cjson This change is because json2 doesn't handle complex objects well. But in this case, a problem is that cjson does not deal with the Time type, so an alias for string is being used --- src/jwt.v | 8 ++++---- src/jwt_test.v | 11 +++++------ src/payload.v | 18 ++++++++++++------ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/jwt.v b/src/jwt.v index ae9a12a..564ab5b 100644 --- a/src/jwt.v +++ b/src/jwt.v @@ -1,7 +1,7 @@ module jwt import encoding.base64 -import x.json2 as json +import json import crypto.hmac import crypto.sha256 import time @@ -14,7 +14,7 @@ pub: } pub fn Token.new[T](payload Payload[T], secret string) Token[T] { - header := base64.url_encode(json.encode[Header](Header{}).bytes()) + header := base64.url_encode(json.encode(Header{}).bytes()) payload_string := base64.url_encode(json.encode(payload).bytes()) signature := base64.url_encode(hmac.new(secret.bytes(), '${header}.${payload_string}'.bytes(), @@ -35,7 +35,7 @@ pub fn from_str[T](token string) !Token[T] { return Token[T]{ header: parts[0] - payload: json.decode[Payload[T]](base64.url_decode_str(parts[1]))! + payload: json.decode(Payload[T], base64.url_decode_str(parts[1]))! signature: parts[2] } } @@ -62,5 +62,5 @@ pub fn (t Token[T]) valid(secret string) bool { } pub fn (t Token[T]) expired() bool { - return t.payload.exp or { return false } < time.now() + return t.payload.exp.time() or { return false } < time.now() } diff --git a/src/jwt_test.v b/src/jwt_test.v index fcb9707..952d7c0 100644 --- a/src/jwt_test.v +++ b/src/jwt_test.v @@ -33,7 +33,7 @@ fn test_expired() { payload := Payload[map[string]string]{ sub: '1234567890' ext: jwt.claims - exp: time.parse('2019-01-01 00:00:00') or { panic(err) } + exp: '2019-01-01 00:00:00' } token := Token.new(payload, jwt.secret) @@ -45,7 +45,7 @@ fn test_no_expired() { payload := Payload[map[string]string]{ sub: '1234567890' ext: jwt.claims - exp: time.now().add_seconds(10) + exp: time.now().add_seconds(10).str() } token := Token.new(payload, jwt.secret) @@ -64,10 +64,9 @@ fn test_from_str() { } token := Token.new(payload, jwt.secret) - token2 := from_str[map[string]string](token.str())! + token2 := from_str[map[string]string](token.str())! //Não está funcionando token3 := Token.new(payload2, jwt.secret) - // error compiler - // assert token2 == token - // assert token2 != token3 + assert token2 == token + assert token2 != token3 } diff --git a/src/payload.v b/src/payload.v index 27859d0..478ee25 100644 --- a/src/payload.v +++ b/src/payload.v @@ -1,13 +1,19 @@ module jwt -import time +import time { Time } + +type JsTime = string pub struct Payload[T] { - iss ?string @[omitempty] - sub ?string @[omitempty] - aud ?string @[omitempty] - exp ?time.Time @[omitempty] - iat ?time.Time @[omitempty] + iss ?string @[omitempty] + sub ?string @[omitempty] + aud ?string @[omitempty] + exp JsTime @[omitempty] + iat JsTime @[omitempty] pub: ext T @[omitempty] } + +pub fn (jst JsTime) time() ?Time { + return time.parse(jst) or { none } +} From 719deaea4619375fcff52babf86d753f9f55a57a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20ldev?= Date: Mon, 19 Feb 2024 12:38:35 -0300 Subject: [PATCH 4/5] adjust access of fields --- src/payload.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/payload.v b/src/payload.v index 478ee25..5a129ff 100644 --- a/src/payload.v +++ b/src/payload.v @@ -5,12 +5,12 @@ import time { Time } type JsTime = string pub struct Payload[T] { +pub: iss ?string @[omitempty] sub ?string @[omitempty] aud ?string @[omitempty] exp JsTime @[omitempty] iat JsTime @[omitempty] -pub: ext T @[omitempty] } From ebda91d198391c707837927f3520799814bb1353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luiz?= Date: Sat, 2 Aug 2025 22:47:33 -0300 Subject: [PATCH 5/5] Refactor time import and JsTime parsing in payload.v Changed import of 'time' to import the whole module instead of just 'Time'. Updated JsTime's time() method to use 'time.Time' type and improved error handling by using 'return none'. Minor formatting adjustments for consistency. --- src/payload.v | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/payload.v b/src/payload.v index 5a129ff..512a07f 100644 --- a/src/payload.v +++ b/src/payload.v @@ -1,6 +1,6 @@ module jwt -import time { Time } +import time type JsTime = string @@ -11,9 +11,9 @@ pub: aud ?string @[omitempty] exp JsTime @[omitempty] iat JsTime @[omitempty] - ext T @[omitempty] + ext T @[omitempty] } -pub fn (jst JsTime) time() ?Time { - return time.parse(jst) or { none } +pub fn (jst JsTime) time() ?time.Time { + return time.parse(jst) or { return none } }