Merge branch 'development' of github.com:Incubaid/herolib into development
* 'development' of github.com:Incubaid/herolib: refactor: Simplify handler signatures and add server runner fix: improve Redis response parsing and error handling fix: Correct AGEClient method receivers and error syntax
This commit is contained in:
@@ -38,7 +38,7 @@ signing_keypair := age_client.generate_signing_keypair() or {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
!signed := age_client.sign(signing_keypair.sign_key, message) or {
|
signed := age_client.sign(signing_keypair.sign_key, message) or {
|
||||||
println('Error signing message: ${err}')
|
println('Error signing message: ${err}')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ verified := age_client.verify(signing_keypair.verify_key, message, signed.signat
|
|||||||
println('Error verifying signature: ${err}')
|
println('Error verifying signature: ${err}')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
!println('Message: ${message}')
|
println('Message: ${message}')
|
||||||
println('Signature: ${signed.signature}')
|
println('Signature: ${signed.signature}')
|
||||||
println('Signature valid: ${verified}')
|
println('Signature valid: ${verified}')
|
||||||
|
|
||||||
|
|||||||
6
examples/hero/heroserver/heroserver.vsh
Executable file
6
examples/hero/heroserver/heroserver.vsh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||||
|
|
||||||
|
import freeflowuniverse.herolib.hero.heroserver
|
||||||
|
|
||||||
|
mut server := heroserver.new_server(port: 8080)!
|
||||||
|
server.start()!
|
||||||
@@ -83,5 +83,16 @@ pub fn (mut r Redis) send_expect_list(items []string) ![]resp.RValue {
|
|||||||
}
|
}
|
||||||
r.write_cmds(items)!
|
r.write_cmds(items)!
|
||||||
res := r.get_response()!
|
res := r.get_response()!
|
||||||
|
|
||||||
|
// Check if we got an error response
|
||||||
|
if res is resp.RError {
|
||||||
|
return error('Redis error: ${res.value}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we got an array response
|
||||||
|
if res !is resp.RArray {
|
||||||
|
return error('Expected array response but got ${res.type_name()}. Response: ${resp.get_redis_value(res)}')
|
||||||
|
}
|
||||||
|
|
||||||
return resp.get_redis_array(res)
|
return resp.get_redis_array(res)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ pub fn (v RValue) u32() u32 {
|
|||||||
pub fn (v RValue) strget() string {
|
pub fn (v RValue) strget() string {
|
||||||
match v {
|
match v {
|
||||||
RInt {
|
RInt {
|
||||||
return v.str()
|
return v.value.str()
|
||||||
}
|
}
|
||||||
// RArray{
|
// RArray{
|
||||||
// return v.str()
|
// return v.str()
|
||||||
@@ -112,10 +112,16 @@ pub fn (v RValue) strget() string {
|
|||||||
return v.value.bytestr()
|
return v.value.bytestr()
|
||||||
}
|
}
|
||||||
RString {
|
RString {
|
||||||
return v.str()
|
return v.value
|
||||||
|
}
|
||||||
|
RError {
|
||||||
|
return v.value
|
||||||
|
}
|
||||||
|
RNil {
|
||||||
|
return ''
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
panic('could not find type')
|
panic('could not find type: ${v.type_name()}')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
module crypt
|
module crypt
|
||||||
|
|
||||||
import freeflowuniverse.herolib.core.redisclient
|
import freeflowuniverse.herolib.data.resp
|
||||||
|
|
||||||
// Stateless AGE operations
|
// Stateless AGE operations
|
||||||
|
|
||||||
// generate_keypair creates a new Age encryption key pair
|
// generate_keypair creates a new Age encryption key pair
|
||||||
pub fn (client &AGEClient) generate_keypair() !KeyPair {
|
pub fn (mut client AGEClient) generate_keypair() !KeyPair {
|
||||||
response := client.redis.send_expect_list(['AGE', 'GENENC'])!
|
response := client.redis.send_expect_list(['AGE', 'GENENC'])!
|
||||||
|
|
||||||
if response.len < 2 {
|
if response.len < 2 {
|
||||||
@@ -13,13 +13,13 @@ pub fn (client &AGEClient) generate_keypair() !KeyPair {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return KeyPair{
|
return KeyPair{
|
||||||
recipient: response[0].str()
|
recipient: response[0].strget()
|
||||||
identity: response[1].str()
|
identity: response[1].strget()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate_signing_keypair creates a new Age signing key pair
|
// generate_signing_keypair creates a new Age signing key pair
|
||||||
pub fn (client &AGEClient) generate_signing_keypair() !SigningKeyPair {
|
pub fn (mut client AGEClient) generate_signing_keypair() !SigningKeyPair {
|
||||||
response := client.redis.send_expect_list(['AGE', 'GENSIGN'])!
|
response := client.redis.send_expect_list(['AGE', 'GENSIGN'])!
|
||||||
|
|
||||||
if response.len < 2 {
|
if response.len < 2 {
|
||||||
@@ -27,13 +27,13 @@ pub fn (client &AGEClient) generate_signing_keypair() !SigningKeyPair {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return SigningKeyPair{
|
return SigningKeyPair{
|
||||||
verify_key: response[0].str()
|
verify_key: response[0].strget()
|
||||||
sign_key: response[1].str()
|
sign_key: response[1].strget()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// encrypt encrypts a message with the recipient's public key
|
// encrypt encrypts a message with the recipient's public key
|
||||||
pub fn (client &AGEClient) encrypt(recipient string, message string) !EncryptionResult {
|
pub fn (mut client AGEClient) encrypt(recipient string, message string) !EncryptionResult {
|
||||||
ciphertext := client.redis.send_expect_str(['AGE', 'ENCRYPT', recipient, message])!
|
ciphertext := client.redis.send_expect_str(['AGE', 'ENCRYPT', recipient, message])!
|
||||||
|
|
||||||
return EncryptionResult{
|
return EncryptionResult{
|
||||||
@@ -42,12 +42,12 @@ pub fn (client &AGEClient) encrypt(recipient string, message string) !Encryption
|
|||||||
}
|
}
|
||||||
|
|
||||||
// decrypt decrypts a message with the identity (private key)
|
// decrypt decrypts a message with the identity (private key)
|
||||||
pub fn (client &AGEClient) decrypt(identity string, ciphertext string) !string {
|
pub fn (mut client AGEClient) decrypt(identity string, ciphertext string) !string {
|
||||||
return client.redis.send_expect_str(['AGE', 'DECRYPT', identity, ciphertext])!
|
return client.redis.send_expect_str(['AGE', 'DECRYPT', identity, ciphertext])!
|
||||||
}
|
}
|
||||||
|
|
||||||
// sign signs a message with the signing key
|
// sign signs a message with the signing key
|
||||||
pub fn (client &AGEClient) sign(sign_key string, message string) !SignatureResult {
|
pub fn (mut client AGEClient) sign(sign_key string, message string) !SignatureResult {
|
||||||
signature := client.redis.send_expect_str(['AGE', 'SIGN', sign_key, message])!
|
signature := client.redis.send_expect_str(['AGE', 'SIGN', sign_key, message])!
|
||||||
|
|
||||||
return SignatureResult{
|
return SignatureResult{
|
||||||
@@ -56,15 +56,15 @@ pub fn (client &AGEClient) sign(sign_key string, message string) !SignatureResul
|
|||||||
}
|
}
|
||||||
|
|
||||||
// verify verifies a signature with the verification key
|
// verify verifies a signature with the verification key
|
||||||
pub fn (client &AGEClient) verify(verify_key string, message string, signature string) !bool {
|
pub fn (mut client AGEClient) verify(verify_key string, message string, signature string) !bool {
|
||||||
result := client.redis.send_expect_int(['AGE', 'VERIFY', verify_key, message, signature])!
|
result := client.redis.send_expect_str(['AGE', 'VERIFY', verify_key, message, signature])!
|
||||||
return result == 1
|
return result == '1'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key-managed AGE operations
|
// Key-managed AGE operations
|
||||||
|
|
||||||
// create_named_keypair creates and stores a named encryption key pair
|
// create_named_keypair creates and stores a named encryption key pair
|
||||||
pub fn (client &AGEClient) create_named_keypair(name string) !KeyPair {
|
pub fn (mut client AGEClient) create_named_keypair(name string) !KeyPair {
|
||||||
response := client.redis.send_expect_list(['AGE', 'KEYGEN', name])!
|
response := client.redis.send_expect_list(['AGE', 'KEYGEN', name])!
|
||||||
|
|
||||||
if response.len < 2 {
|
if response.len < 2 {
|
||||||
@@ -72,13 +72,13 @@ pub fn (client &AGEClient) create_named_keypair(name string) !KeyPair {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return KeyPair{
|
return KeyPair{
|
||||||
recipient: response[0].str()
|
recipient: response[0].strget()
|
||||||
identity: response[1].str()
|
identity: response[1].strget()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create_named_signing_keypair creates and stores a named signing key pair
|
// create_named_signing_keypair creates and stores a named signing key pair
|
||||||
pub fn (client &AGEClient) create_named_signing_keypair(name string) !SigningKeyPair {
|
pub fn (mut client AGEClient) create_named_signing_keypair(name string) !SigningKeyPair {
|
||||||
response := client.redis.send_expect_list(['AGE', 'SIGNKEYGEN', name])!
|
response := client.redis.send_expect_list(['AGE', 'SIGNKEYGEN', name])!
|
||||||
|
|
||||||
if response.len < 2 {
|
if response.len < 2 {
|
||||||
@@ -86,13 +86,13 @@ pub fn (client &AGEClient) create_named_signing_keypair(name string) !SigningKey
|
|||||||
}
|
}
|
||||||
|
|
||||||
return SigningKeyPair{
|
return SigningKeyPair{
|
||||||
verify_key: response[0].str()
|
verify_key: response[0].strget()
|
||||||
sign_key: response[1].str()
|
sign_key: response[1].strget()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// encrypt_with_named_key encrypts a message using a named key
|
// encrypt_with_named_key encrypts a message using a named key
|
||||||
pub fn (client &AGEClient) encrypt_with_named_key(key_name string, message string) !EncryptionResult {
|
pub fn (mut client AGEClient) encrypt_with_named_key(key_name string, message string) !EncryptionResult {
|
||||||
ciphertext := client.redis.send_expect_str(['AGE', 'ENCRYPTNAME', key_name, message])!
|
ciphertext := client.redis.send_expect_str(['AGE', 'ENCRYPTNAME', key_name, message])!
|
||||||
|
|
||||||
return EncryptionResult{
|
return EncryptionResult{
|
||||||
@@ -101,12 +101,12 @@ pub fn (client &AGEClient) encrypt_with_named_key(key_name string, message strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// decrypt_with_named_key decrypts a message using a named key
|
// decrypt_with_named_key decrypts a message using a named key
|
||||||
pub fn (client &AGEClient) decrypt_with_named_key(key_name string, ciphertext string) !string {
|
pub fn (mut client AGEClient) decrypt_with_named_key(key_name string, ciphertext string) !string {
|
||||||
return client.redis.send_expect_str(['AGE', 'DECRYPTNAME', key_name, ciphertext])!
|
return client.redis.send_expect_str(['AGE', 'DECRYPTNAME', key_name, ciphertext])!
|
||||||
}
|
}
|
||||||
|
|
||||||
// sign_with_named_key signs a message using a named signing key
|
// sign_with_named_key signs a message using a named signing key
|
||||||
pub fn (client &AGEClient) sign_with_named_key(key_name string, message string) !SignatureResult {
|
pub fn (mut client AGEClient) sign_with_named_key(key_name string, message string) !SignatureResult {
|
||||||
signature := client.redis.send_expect_str(['AGE', 'SIGNNAME', key_name, message])!
|
signature := client.redis.send_expect_str(['AGE', 'SIGNNAME', key_name, message])!
|
||||||
|
|
||||||
return SignatureResult{
|
return SignatureResult{
|
||||||
@@ -115,18 +115,36 @@ pub fn (client &AGEClient) sign_with_named_key(key_name string, message string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// verify_with_named_key verifies a signature using a named verification key
|
// verify_with_named_key verifies a signature using a named verification key
|
||||||
pub fn (client &AGEClient) verify_with_named_key(key_name string, message string, signature string) !bool {
|
pub fn (mut client AGEClient) verify_with_named_key(key_name string, message string, signature string) !bool {
|
||||||
result := client.redis.send_expect_int(['AGE', 'VERIFYNAME', key_name, message, signature])!
|
result := client.redis.send_expect_str(['AGE', 'VERIFYNAME', key_name, message, signature])!
|
||||||
return result == 1
|
return result == '1'
|
||||||
}
|
}
|
||||||
|
|
||||||
// list_keys lists all stored AGE keys
|
// list_keys lists all stored AGE keys
|
||||||
pub fn (client &AGEClient) list_keys() ![]string {
|
pub fn (mut client AGEClient) list_keys() ![]string {
|
||||||
response := client.redis.send_expect_list(['AGE', 'LIST'])!
|
response := client.redis.send_expect_list(['AGE', 'LIST'])!
|
||||||
|
|
||||||
mut keys := []string{}
|
mut keys := []string{}
|
||||||
for i in 0 .. response.len {
|
for i in 0 .. response.len {
|
||||||
keys << response[i].str()
|
item := response[i]
|
||||||
|
// Handle different response types
|
||||||
|
match item {
|
||||||
|
resp.RString {
|
||||||
|
keys << item.value
|
||||||
|
}
|
||||||
|
resp.RBString {
|
||||||
|
keys << item.value.bytestr()
|
||||||
|
}
|
||||||
|
resp.RArray {
|
||||||
|
// If it's an array, try to get the first element as the key name
|
||||||
|
if item.values.len > 0 {
|
||||||
|
keys << item.values[0].strget()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
keys << item.strget()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return keys
|
return keys
|
||||||
|
|||||||
@@ -6,29 +6,27 @@ and the implementation of the example of how webserver is see lib/schemas/openrp
|
|||||||
specs for the heroserver
|
specs for the heroserver
|
||||||
|
|
||||||
- a factory (without globals) creates a server, based on chosen port
|
- a factory (without globals) creates a server, based on chosen port
|
||||||
- the server does basic authentication and has
|
- the server does basic authentication and has
|
||||||
- register method: pubkey
|
- register method: pubkey
|
||||||
- authreq: pubkey, it returns a unique key (hashed md5 of pubkey + something random). (is a request for authentication)
|
- authreq: pubkey, it returns a unique key (hashed md5 of pubkey + something random). (is a request for authentication)
|
||||||
- auth: the user who wants access signs the unique key from authreq with , and sends the signature to the server, who then knows for sure I know this user, we return as sessionkey
|
- auth: the user who wants access signs the unique key from authreq with , and sends the signature to the server, who then knows for sure I know this user, we return as sessionkey
|
||||||
- all consequent requests need to use this sessionkey, so the server knows who is doing the requests
|
- all consequent requests need to use this sessionkey, so the server knows who is doing the requests
|
||||||
- the server serves the openrpc api behind api/$handlertype/... the $handlertype is per handler type, so we can register more than 1 openrpc hander
|
- the server serves the openrpc api behind api/$handlertype/... the $handlertype is per handler type, so we can register more than 1 openrpc hander
|
||||||
- the server serves an html endpoint for doc/$handlertype/
|
- the server serves an html endpoint for doc/$handlertype/
|
||||||
|
|
||||||
|
|
||||||
for the doc (html endpoint)
|
for the doc (html endpoint)
|
||||||
|
|
||||||
- use bootstrap from cdn
|
- use bootstrap from cdn
|
||||||
- https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css
|
- <https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css>
|
||||||
- https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js
|
- <https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js>
|
||||||
- https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js
|
- <https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js>
|
||||||
- create an html template in lib/hero/heroserver/template/doc.md (template for v language)
|
- create an html template in lib/hero/heroserver/template/doc.md (template for v language)
|
||||||
- the template uses the openrpc spec obj comes from lib/schemas/openrpc and lib/schemas/jsonrpc for the schema's
|
- the template uses the openrpc spec obj comes from lib/schemas/openrpc and lib/schemas/jsonrpc for the schema's
|
||||||
- so first fo the spec decode to the object from schemas/openrpc then use this obj to populate the template
|
- so first fo the spec decode to the object from schemas/openrpc then use this obj to populate the template
|
||||||
- in the template make a header 1 for each rootobject e.g. calendar, then dense show the methods with directly in the method a dense representation of the params and return
|
- in the template make a header 1 for each rootobject e.g. calendar, then dense show the methods with directly in the method a dense representation of the params and return
|
||||||
- each object e.g. pub fn (self Comment) description(methodname string) string { has a description and pub fn (self Comment) example(methodname string) (string, string) wich returns description per method, if not filled in then its for the full rootobject, and also example, make sure to use those in the template for the documentation
|
- each object e.g. pub fn (self Comment) description(methodname string) string { has a description and pub fn (self Comment) example(methodname string) (string, string) wich returns description per method, if not filled in then its for the full rootobject, and also example, make sure to use those in the template for the documentation
|
||||||
- the template is markdown, we will have to use a .md to .html conversion (best to do in browser itself) and get .md from the webserver directly and convert
|
- the template is markdown, we will have to use a .md to .html conversion (best to do in browser itself) and get .md from the webserver directly and convert
|
||||||
- the purpose is to have a very nice documentation done per object so we know what the object does, and how to use it
|
- the purpose is to have a very nice documentation done per object so we know what the object does, and how to use it
|
||||||
|
|
||||||
make clear instructions what code needs to be written and which steps are needed
|
make clear instructions what code needs to be written and which steps are needed
|
||||||
we are in architecture mode
|
we are in architecture mode
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
module heroserver
|
module heroserver
|
||||||
|
|
||||||
|
|
||||||
@[params]
|
@[params]
|
||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
pub:
|
pub:
|
||||||
port int = 8080
|
port int = 8080
|
||||||
host string = 'localhost'
|
host string = 'localhost'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Factory function to create new server instance
|
// Factory function to create new server instance
|
||||||
pub fn new_server(config ServerConfig) !&HeroServer {
|
pub fn new_server(config ServerConfig) !&HeroServer {
|
||||||
mut server := &HeroServer{
|
return &HeroServer{
|
||||||
config: config
|
config: config
|
||||||
auth_manager: new_auth_manager()
|
auth_manager: new_auth_manager()
|
||||||
handler_registry: new_handler_registry()
|
handler_registry: new_handler_registry()
|
||||||
}
|
}
|
||||||
return server
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -50,13 +50,9 @@ pub fn (mut s HeroServer) auth(mut ctx Context) veb.Result {
|
|||||||
|
|
||||||
// API endpoints
|
// API endpoints
|
||||||
@['/api/:handler_type'; post]
|
@['/api/:handler_type'; post]
|
||||||
pub fn (mut s HeroServer) api(mut ctx Context) veb.Result {
|
pub fn (mut s HeroServer) api(mut ctx Context, handler_type string) veb.Result {
|
||||||
handler_type := ctx.params['handler_type'] or {
|
|
||||||
return ctx.request_error('handler_type not found in params')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate session
|
// Validate session
|
||||||
session_key := ctx.req.header.get('Authorization') or { '' }
|
session_key := ctx.get_custom_header('Authorization') or { '' }
|
||||||
if !s.auth_manager.validate_session(session_key) {
|
if !s.auth_manager.validate_session(session_key) {
|
||||||
return ctx.request_error('Invalid session')
|
return ctx.request_error('Invalid session')
|
||||||
}
|
}
|
||||||
@@ -75,11 +71,7 @@ pub fn (mut s HeroServer) api(mut ctx Context) veb.Result {
|
|||||||
|
|
||||||
// Documentation endpoints
|
// Documentation endpoints
|
||||||
@['/doc/:handler_type'; get]
|
@['/doc/:handler_type'; get]
|
||||||
pub fn (mut s HeroServer) doc(mut ctx Context) veb.Result {
|
pub fn (mut s HeroServer) doc(mut ctx Context, handler_type string) veb.Result {
|
||||||
handler_type := ctx.params['handler_type'] or {
|
|
||||||
return ctx.request_error('handler_type not found in params')
|
|
||||||
}
|
|
||||||
|
|
||||||
handler := s.handler_registry.get(handler_type) or { return ctx.not_found() }
|
handler := s.handler_registry.get(handler_type) or { return ctx.not_found() }
|
||||||
|
|
||||||
doc_html := s.generate_documentation(handler_type, handler) or {
|
doc_html := s.generate_documentation(handler_type, handler) or {
|
||||||
|
|||||||
Reference in New Issue
Block a user