Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,99 @@
|
||||
// vtest retry: 3
|
||||
import db.sqlite
|
||||
import x.json2 as json
|
||||
import os
|
||||
import time
|
||||
import x.sessions
|
||||
|
||||
const max_age = time.second
|
||||
const db_path = os.join_path(os.vtmp_dir(), 'x_sessions_db_test.db')
|
||||
|
||||
fn testsuite_begin() {
|
||||
os.rm(db_path) or {}
|
||||
}
|
||||
|
||||
fn testsuite_end() {
|
||||
os.rm(db_path) or {}
|
||||
}
|
||||
|
||||
pub struct User {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
const default_user = User{
|
||||
name: 'john'
|
||||
age: 99
|
||||
}
|
||||
const default_user_encoded = json.encode(default_user)
|
||||
|
||||
fn test_store_set() {
|
||||
mut db := get_connection()!
|
||||
defer {
|
||||
db.close() or {}
|
||||
}
|
||||
mut store := sessions.DBStore.create[User](db)!
|
||||
store.set('a', default_user)!
|
||||
|
||||
mut rows := sql db {
|
||||
select from sessions.DBStoreSessions
|
||||
}!
|
||||
assert rows.len == 1
|
||||
|
||||
// check if created at time is not empty
|
||||
assert rows[0].created_at != time.Time{}
|
||||
assert rows[0].data == default_user_encoded
|
||||
|
||||
first_created := rows[0].created_at
|
||||
store.set('a', User{ age: 99 })!
|
||||
|
||||
rows = sql db {
|
||||
select from sessions.DBStoreSessions
|
||||
}!
|
||||
assert rows.len == 1
|
||||
assert rows[0].created_at == first_created
|
||||
assert rows[0].data == json.encode(User{ age: 99 })
|
||||
}
|
||||
|
||||
fn test_store_get() {
|
||||
mut db := get_connection()!
|
||||
defer {
|
||||
db.close() or {}
|
||||
}
|
||||
mut store := sessions.DBStore.create[User](db)!
|
||||
store.set('b', default_user)!
|
||||
|
||||
if data := store.get('b', max_age) {
|
||||
assert data == default_user
|
||||
} else {
|
||||
$if !windows {
|
||||
assert false, 'session data should not be none'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_store_session_expired() {
|
||||
mut db := get_connection()!
|
||||
defer {
|
||||
db.close() or {}
|
||||
}
|
||||
mut store := sessions.DBStore.create[User](db)!
|
||||
store.set('c', default_user)!
|
||||
|
||||
time.sleep(2 * max_age)
|
||||
|
||||
if data := store.get('c', max_age) {
|
||||
assert false, 'session should be expired!'
|
||||
} else {
|
||||
assert err.msg() == 'session is expired'
|
||||
}
|
||||
// verify that data is deleted
|
||||
rows := sql db {
|
||||
select from sessions.DBStoreSessions where session_id == 'c'
|
||||
}!
|
||||
assert rows.len == 0
|
||||
}
|
||||
|
||||
fn get_connection() !sqlite.DB {
|
||||
return sqlite.connect(db_path)!
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import time
|
||||
import x.sessions
|
||||
|
||||
const max_age = time.second
|
||||
|
||||
pub struct User {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
const default_user = User{
|
||||
name: 'john'
|
||||
age: 99
|
||||
}
|
||||
|
||||
fn test_store_set() {
|
||||
mut store := sessions.MemoryStore[User]{}
|
||||
store.set('a', default_user)!
|
||||
|
||||
// check if created at time is not empty
|
||||
assert store.data['a'].created_at != time.Time{}
|
||||
assert store.data['a'].data == default_user
|
||||
|
||||
first_created := store.data['a'].created_at
|
||||
store.set('a', User{ age: 99 })!
|
||||
|
||||
assert store.data['a'].created_at == first_created
|
||||
assert store.data['a'].data.age == 99
|
||||
}
|
||||
|
||||
fn test_store_get() {
|
||||
mut store := sessions.MemoryStore[User]{}
|
||||
store.set('a', default_user)!
|
||||
|
||||
if data := store.get('a', max_age) {
|
||||
assert data == default_user
|
||||
} else {
|
||||
assert false, 'session data should not be none'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_store_session_expired() {
|
||||
mut store := sessions.MemoryStore[User]{}
|
||||
store.set('a', default_user)!
|
||||
|
||||
time.sleep(2 * max_age)
|
||||
|
||||
if data := store.get('a', max_age) {
|
||||
assert false, 'session should be expired!'
|
||||
} else {
|
||||
assert err.msg() == 'session is expired'
|
||||
}
|
||||
assert store.data.len == 0
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
// vtest retry: 2
|
||||
import net.http
|
||||
import time
|
||||
import x.sessions
|
||||
import veb
|
||||
import x.sessions.veb_middleware
|
||||
|
||||
const port = 13010
|
||||
const localserver = 'http://127.0.0.1:${port}'
|
||||
const exit_after = time.second * 10
|
||||
const cookie_name = 'SESSION_ID'
|
||||
const max_age = time.second * 2
|
||||
|
||||
pub struct User {
|
||||
pub mut:
|
||||
name string
|
||||
age int
|
||||
verified bool
|
||||
}
|
||||
|
||||
const default_user = User{
|
||||
name: 'casper'
|
||||
age: 21
|
||||
verified: false
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
sessions.CurrentSession[User]
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
veb.Middleware[Context]
|
||||
pub mut:
|
||||
sessions &sessions.Sessions[User]
|
||||
started chan bool
|
||||
}
|
||||
|
||||
pub fn (mut app App) before_accept_loop() {
|
||||
app.started <- true
|
||||
}
|
||||
|
||||
pub fn (app &App) session_data(mut ctx Context) veb.Result {
|
||||
return ctx.text('${ctx.session_data}')
|
||||
}
|
||||
|
||||
pub fn (app &App) protected(mut ctx Context) veb.Result {
|
||||
if user := ctx.session_data {
|
||||
return ctx.json(user)
|
||||
} else {
|
||||
ctx.res.set_status(.unauthorized)
|
||||
return ctx.text('No session saved!')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut app App) save_session(mut ctx Context) veb.Result {
|
||||
app.sessions.save(mut ctx, default_user) or { return ctx.server_error(err.msg()) }
|
||||
return ctx.ok('')
|
||||
}
|
||||
|
||||
pub fn (mut app App) update_session(mut ctx Context) veb.Result {
|
||||
if mut user := ctx.session_data {
|
||||
user.age++
|
||||
app.sessions.save(mut ctx, user) or { return ctx.server_error(err.msg()) }
|
||||
// sessions module should also update the context
|
||||
if new_user := ctx.session_data {
|
||||
assert new_user == user, 'session data is not updated on the context'
|
||||
} else {
|
||||
assert false, 'session data should not be none'
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.ok('')
|
||||
}
|
||||
|
||||
pub fn (mut app App) destroy_session(mut ctx Context) veb.Result {
|
||||
app.sessions.destroy(mut ctx) or { return ctx.server_error(err.msg()) }
|
||||
// sessions module should also update the context
|
||||
assert ctx.session_data == none
|
||||
|
||||
return ctx.ok('')
|
||||
}
|
||||
|
||||
fn testsuite_begin() {
|
||||
spawn fn () {
|
||||
time.sleep(exit_after)
|
||||
assert false, 'timeout reached!'
|
||||
exit(1)
|
||||
}()
|
||||
|
||||
mut app := &App{
|
||||
sessions: &sessions.Sessions[User]{
|
||||
store: sessions.MemoryStore[User]{}
|
||||
secret: 'secret'.bytes()
|
||||
cookie_options: sessions.CookieOptions{
|
||||
cookie_name: cookie_name
|
||||
}
|
||||
max_age: max_age
|
||||
}
|
||||
}
|
||||
|
||||
app.use(veb_middleware.create[User, Context](mut app.sessions))
|
||||
|
||||
spawn veb.run_at[App, Context](mut app, port: port, family: .ip, timeout_in_seconds: 2)
|
||||
// app startup time
|
||||
_ := <-app.started
|
||||
wait_for_server()!
|
||||
}
|
||||
|
||||
fn test_empty_session() {
|
||||
x := http.get('${localserver}/session_data')!
|
||||
|
||||
assert x.body == 'Option(none)'
|
||||
}
|
||||
|
||||
fn test_protected_without_session() {
|
||||
x := http.get('${localserver}/protected')!
|
||||
|
||||
assert x.status() == .unauthorized
|
||||
}
|
||||
|
||||
fn test_save_session() {
|
||||
sid := get_session_id()!
|
||||
x := make_request_with_session_id(.get, '/session_data', sid)!
|
||||
|
||||
// cast to `?User` since, session_data is of type `?T`
|
||||
assert x.body == '${?User(default_user)}'
|
||||
}
|
||||
|
||||
fn test_update_session() {
|
||||
sid := get_session_id()!
|
||||
|
||||
mut x := make_request_with_session_id(.get, '/update_session', sid)!
|
||||
x = make_request_with_session_id(.get, '/session_data', sid)!
|
||||
|
||||
mut updated_user := default_user
|
||||
updated_user.age++
|
||||
assert x.body == '${?User(updated_user)}'
|
||||
}
|
||||
|
||||
fn test_destroy_session() {
|
||||
sid := get_session_id()!
|
||||
mut x := make_request_with_session_id(.get, '/session_data', sid)!
|
||||
assert x.body != 'Option(none)'
|
||||
|
||||
x = make_request_with_session_id(.get, '/destroy_session', sid)!
|
||||
assert x.status() == .ok
|
||||
|
||||
x = make_request_with_session_id(.get, '/session_data', sid)!
|
||||
assert x.body == 'Option(none)'
|
||||
}
|
||||
|
||||
fn test_session_expired() {
|
||||
sid := get_session_id()!
|
||||
mut x := make_request_with_session_id(.get, '/session_data', sid)!
|
||||
assert x.body != 'Option(none)'
|
||||
|
||||
time.sleep(max_age)
|
||||
|
||||
x = make_request_with_session_id(.get, '/session_data', sid)!
|
||||
assert x.body == 'Option(none)'
|
||||
}
|
||||
|
||||
// Utility
|
||||
|
||||
fn get_session_id() !string {
|
||||
x := http.get('${localserver}/save_session')!
|
||||
|
||||
return x.cookies()[0].value
|
||||
}
|
||||
|
||||
fn make_request_with_session_id(method http.Method, path string, sid string) !http.Response {
|
||||
return http.fetch(http.FetchConfig{
|
||||
url: '${localserver}${path}'
|
||||
method: method
|
||||
cookies: {
|
||||
cookie_name: sid
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn wait_for_server() ! {
|
||||
for _ in 0 .. 40 {
|
||||
response := http.get('${localserver}/session_data') or {
|
||||
time.sleep(50 * time.millisecond)
|
||||
continue
|
||||
}
|
||||
if response.status() == .ok {
|
||||
return
|
||||
}
|
||||
time.sleep(50 * time.millisecond)
|
||||
}
|
||||
return error('session test server did not start in time')
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import time
|
||||
import x.sessions
|
||||
|
||||
const max_age = time.second
|
||||
const secret = 'session_test'.bytes()
|
||||
|
||||
pub struct User {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
const default_user = User{
|
||||
name: 'john'
|
||||
age: 99
|
||||
}
|
||||
|
||||
fn test_session_id() {
|
||||
unverified_sid, sid_with_hmac := sessions.new_session_id(secret)
|
||||
verified_sid, valid := sessions.verify_session_id(sid_with_hmac, secret)
|
||||
|
||||
assert unverified_sid == verified_sid
|
||||
assert valid
|
||||
}
|
||||
|
||||
fn test_forged_signature_rejected() {
|
||||
// Create a valid session ID
|
||||
sid, _ := sessions.new_session_id(secret)
|
||||
|
||||
// Forge a cookie with valid session ID but invalid signature
|
||||
forged_cookie := '${sid}.INVALID_SIGNATURE'
|
||||
verified_sid, valid := sessions.verify_session_id(forged_cookie, secret)
|
||||
|
||||
// Forged signature must be rejected
|
||||
assert !valid
|
||||
assert verified_sid == sid
|
||||
}
|
||||
|
||||
fn test_wrong_secret_rejected() {
|
||||
// Create a session with one secret
|
||||
_, signed_cookie := sessions.new_session_id(secret)
|
||||
|
||||
// Try to verify with a different secret
|
||||
wrong_secret := 'wrong_secret'.bytes()
|
||||
_, valid := sessions.verify_session_id(signed_cookie, wrong_secret)
|
||||
|
||||
// Must be rejected when using wrong secret
|
||||
assert !valid
|
||||
}
|
||||
|
||||
fn test_malformed_cookie_rejected() {
|
||||
// Cookie without signature separator
|
||||
_, valid1 := sessions.verify_session_id('just_a_session_id', secret)
|
||||
assert !valid1
|
||||
|
||||
// Empty cookie
|
||||
_, valid2 := sessions.verify_session_id('', secret)
|
||||
assert !valid2
|
||||
|
||||
// Cookie with empty parts
|
||||
_, valid3 := sessions.verify_session_id('.', secret)
|
||||
assert !valid3
|
||||
}
|
||||
Reference in new issue
Block a user