Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
```
4 changes: 2 additions & 2 deletions src/header.v
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module jwt

pub struct Header {
alg string = "HS256"
typ string = "JWT"
alg string = 'HS256'
typ string = 'JWT'
}
71 changes: 31 additions & 40 deletions src/jwt.v
Original file line number Diff line number Diff line change
@@ -1,75 +1,66 @@
module jwt

import encoding.base64
import x.json2 as json
import json
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 {
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())
pub fn Token.new[T](payload Payload[T], secret string) Token[T] {
header := base64.url_encode(json.encode(Header{}).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.time() or { return false } < time.now()
}
73 changes: 49 additions & 24 deletions src/jwt_test.v
Original file line number Diff line number Diff line change
@@ -1,46 +1,71 @@
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: '2019-01-01 00:00:00'
}
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).str()
}
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())! //Não está funcionando
token3 := Token.new(payload2, jwt.secret)

assert token2 == token
assert token2 != token3
Expand Down
15 changes: 11 additions & 4 deletions src/payload.v
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ module jwt

import time

pub struct Payload {
type JsTime = string

pub struct Payload[T] {
pub:
iss ?string @[omitempty]
sub ?string @[omitempty]
aud ?string @[omitempty]
exp ?time.Time @[omitempty]
iat ?time.Time @[omitempty]
ext ?string @[omitempty]
exp JsTime @[omitempty]
iat JsTime @[omitempty]
ext T @[omitempty]
}

pub fn (jst JsTime) time() ?time.Time {
return time.parse(jst) or { return none }
}