mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-07-03 09:47:38 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f86eb0f06 | |||
| 3dac55cb19 | |||
| bbb2c6d127 | |||
| 3fec7b1a90 | |||
| 910390f62d | |||
| b9bed67bd7 |
@@ -50,6 +50,10 @@ type ServerInstanceConfig struct {
|
||||
// GZIP enables GZIP compression middleware
|
||||
GZIP bool `mapstructure:"gzip"`
|
||||
|
||||
// HTTP2 enables HTTP/2 with the Extended CONNECT protocol (RFC 8441) for WebSocket support.
|
||||
// Requires TLS; pair with SSLCert/SSLKey, SelfSignedSSL, or AutoTLS.
|
||||
HTTP2 bool `mapstructure:"http2"`
|
||||
|
||||
// TLS/HTTPS configuration options (mutually exclusive)
|
||||
// Option 1: Provide certificate and key files directly
|
||||
SSLCert string `mapstructure:"ssl_cert"`
|
||||
|
||||
@@ -2711,9 +2711,12 @@ func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{
|
||||
}
|
||||
|
||||
w.SetHeader("Content-Type", "application/json")
|
||||
w.SetHeader("Content-Range", fmt.Sprintf("%d-%d/%d", metadata.Offset, int64(metadata.Offset)+metadata.Count, metadata.Filtered))
|
||||
w.SetHeader("Content-Range", fmt.Sprintf("items %d-%d/%d", metadata.Offset, int64(metadata.Offset)+metadata.Count, metadata.Filtered))
|
||||
w.SetHeader("X-Api-Range-Total", fmt.Sprintf("%d", metadata.Filtered))
|
||||
w.SetHeader("X-Api-Range-Size", fmt.Sprintf("%d", metadata.Count))
|
||||
w.SetHeader("X-Api-Range-From", fmt.Sprintf("%d", metadata.Offset))
|
||||
w.SetHeader("X-Api-Range-Etotal", fmt.Sprintf("%d", metadata.Filtered))
|
||||
w.SetHeader("X-Api-Modelname", tableName)
|
||||
|
||||
// Format response based on response format option
|
||||
switch options.ResponseFormat {
|
||||
|
||||
@@ -225,12 +225,13 @@ func (h *Handler) parseOptionsFromHeaders(r common.Request, model interface{}) E
|
||||
limitValueParts := strings.Split(limitValue, ",")
|
||||
|
||||
if len(limitValueParts) > 1 {
|
||||
if offset, err := strconv.Atoi(limitValueParts[0]); err == nil {
|
||||
options.Offset = &offset
|
||||
}
|
||||
if limit, err := strconv.Atoi(limitValueParts[1]); err == nil {
|
||||
if limit, err := strconv.Atoi(limitValueParts[0]); err == nil {
|
||||
options.Limit = &limit
|
||||
}
|
||||
if offset, err := strconv.Atoi(limitValueParts[1]); err == nil {
|
||||
options.Offset = &offset
|
||||
}
|
||||
|
||||
} else {
|
||||
if limit, err := strconv.Atoi(limitValueParts[0]); err == nil {
|
||||
options.Limit = &limit
|
||||
|
||||
@@ -13,6 +13,9 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login_at TIMESTAMP,
|
||||
-- Program-level user mapping
|
||||
program_user_id INTEGER DEFAULT 0,
|
||||
program_user_table VARCHAR(255) DEFAULT '',
|
||||
-- OAuth2 fields
|
||||
remote_id VARCHAR(255), -- Provider's user ID (e.g., Google sub, GitHub id)
|
||||
auth_provider VARCHAR(50), -- 'local', 'google', 'github', 'microsoft', 'facebook', etc.
|
||||
@@ -99,6 +102,8 @@ DECLARE
|
||||
v_expires_at TIMESTAMP;
|
||||
v_ip_address TEXT;
|
||||
v_user_agent TEXT;
|
||||
v_program_user_id INTEGER;
|
||||
v_program_user_table TEXT;
|
||||
BEGIN
|
||||
-- Extract login request fields
|
||||
v_username := p_request->>'username';
|
||||
@@ -106,8 +111,8 @@ BEGIN
|
||||
v_user_agent := p_request->'claims'->>'user_agent';
|
||||
|
||||
-- Validate user credentials
|
||||
SELECT id, username, email, password, user_level, roles
|
||||
INTO v_user_id, v_username, v_email, v_password_hash, v_user_level, v_roles
|
||||
SELECT id, username, email, password, user_level, roles, program_user_id, program_user_table
|
||||
INTO v_user_id, v_username, v_email, v_password_hash, v_user_level, v_roles, v_program_user_id, v_program_user_table
|
||||
FROM users
|
||||
WHERE username = v_username AND is_active = true;
|
||||
|
||||
@@ -146,7 +151,9 @@ BEGIN
|
||||
'email', v_email,
|
||||
'user_level', v_user_level,
|
||||
'roles', string_to_array(COALESCE(v_roles, ''), ','),
|
||||
'session_id', v_session_token
|
||||
'session_id', v_session_token,
|
||||
'program_user_id', COALESCE(v_program_user_id, 0),
|
||||
'program_user_table', COALESCE(v_program_user_table, '')
|
||||
),
|
||||
'expires_in', 86400 -- 24 hours in seconds
|
||||
);
|
||||
@@ -195,12 +202,16 @@ DECLARE
|
||||
v_user_level INTEGER;
|
||||
v_roles TEXT;
|
||||
v_session_id TEXT;
|
||||
v_program_user_id INTEGER;
|
||||
v_program_user_table TEXT;
|
||||
BEGIN
|
||||
-- Query session and user data
|
||||
SELECT
|
||||
s.user_id, u.username, u.email, u.user_level, u.roles, s.session_token
|
||||
s.user_id, u.username, u.email, u.user_level, u.roles, s.session_token,
|
||||
u.program_user_id, u.program_user_table
|
||||
INTO
|
||||
v_user_id, v_username, v_email, v_user_level, v_roles, v_session_id
|
||||
v_user_id, v_username, v_email, v_user_level, v_roles, v_session_id,
|
||||
v_program_user_id, v_program_user_table
|
||||
FROM user_sessions s
|
||||
JOIN users u ON s.user_id = u.id
|
||||
WHERE s.session_token = p_session_token
|
||||
@@ -222,7 +233,9 @@ BEGIN
|
||||
'email', v_email,
|
||||
'user_level', v_user_level,
|
||||
'session_id', v_session_id,
|
||||
'roles', string_to_array(COALESCE(v_roles, ''), ',')
|
||||
'roles', string_to_array(COALESCE(v_roles, ''), ','),
|
||||
'program_user_id', COALESCE(v_program_user_id, 0),
|
||||
'program_user_table', COALESCE(v_program_user_table, '')
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
@@ -266,10 +279,14 @@ DECLARE
|
||||
v_expires_at TIMESTAMP;
|
||||
v_ip_address TEXT;
|
||||
v_user_agent TEXT;
|
||||
v_program_user_id INTEGER;
|
||||
v_program_user_table TEXT;
|
||||
BEGIN
|
||||
-- Verify old session exists and is valid
|
||||
SELECT s.user_id, u.username, u.email, u.user_level, u.roles, s.ip_address, s.user_agent
|
||||
INTO v_user_id, v_username, v_email, v_user_level, v_roles, v_ip_address, v_user_agent
|
||||
SELECT s.user_id, u.username, u.email, u.user_level, u.roles, s.ip_address, s.user_agent,
|
||||
u.program_user_id, u.program_user_table
|
||||
INTO v_user_id, v_username, v_email, v_user_level, v_roles, v_ip_address, v_user_agent,
|
||||
v_program_user_id, v_program_user_table
|
||||
FROM user_sessions s
|
||||
JOIN users u ON s.user_id = u.id
|
||||
WHERE s.session_token = p_old_session_token
|
||||
@@ -302,7 +319,9 @@ BEGIN
|
||||
'email', v_email,
|
||||
'user_level', v_user_level,
|
||||
'session_id', v_new_session_token,
|
||||
'roles', string_to_array(COALESCE(v_roles, ''), ',')
|
||||
'roles', string_to_array(COALESCE(v_roles, ''), ','),
|
||||
'program_user_id', COALESCE(v_program_user_id, 0),
|
||||
'program_user_table', COALESCE(v_program_user_table, '')
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
@@ -439,6 +458,8 @@ DECLARE
|
||||
v_ip_address TEXT;
|
||||
v_user_agent TEXT;
|
||||
v_roles_array TEXT[];
|
||||
v_program_user_id INTEGER;
|
||||
v_program_user_table TEXT;
|
||||
BEGIN
|
||||
-- Extract registration request fields
|
||||
v_username := p_request->>'username';
|
||||
@@ -447,6 +468,8 @@ BEGIN
|
||||
v_user_level := COALESCE((p_request->>'user_level')::integer, 0);
|
||||
v_ip_address := p_request->'claims'->>'ip_address';
|
||||
v_user_agent := p_request->'claims'->>'user_agent';
|
||||
v_program_user_id := COALESCE((p_request->>'program_user_id')::integer, 0);
|
||||
v_program_user_table := COALESCE(p_request->>'program_user_table', '');
|
||||
|
||||
-- Convert roles array from JSON to comma-separated string
|
||||
SELECT array_to_string(ARRAY(SELECT jsonb_array_elements_text(p_request->'roles')), ',')
|
||||
@@ -485,8 +508,8 @@ BEGIN
|
||||
-- v_password := crypt(v_password, gen_salt('bf'));
|
||||
|
||||
-- Create new user
|
||||
INSERT INTO users (username, email, password, user_level, roles, is_active, created_at, updated_at)
|
||||
VALUES (v_username, v_email, v_password, v_user_level, v_roles, true, now(), now())
|
||||
INSERT INTO users (username, email, password, user_level, roles, is_active, created_at, updated_at, program_user_id, program_user_table)
|
||||
VALUES (v_username, v_email, v_password, v_user_level, v_roles, true, now(), now(), v_program_user_id, v_program_user_table)
|
||||
RETURNING id INTO v_user_id;
|
||||
|
||||
-- Generate session token
|
||||
@@ -512,7 +535,9 @@ BEGIN
|
||||
'email', v_email,
|
||||
'user_level', v_user_level,
|
||||
'roles', string_to_array(COALESCE(v_roles, ''), ','),
|
||||
'session_id', v_session_token
|
||||
'session_id', v_session_token,
|
||||
'program_user_id', v_program_user_id,
|
||||
'program_user_table', v_program_user_table
|
||||
),
|
||||
'expires_in', 86400 -- 24 hours in seconds
|
||||
);
|
||||
@@ -671,12 +696,16 @@ DECLARE
|
||||
v_user_level INTEGER;
|
||||
v_roles TEXT;
|
||||
v_expires_at TIMESTAMP;
|
||||
v_program_user_id INTEGER;
|
||||
v_program_user_table TEXT;
|
||||
BEGIN
|
||||
-- Query session and user data from user_sessions table
|
||||
SELECT
|
||||
s.user_id, u.username, u.email, u.user_level, u.roles, s.expires_at
|
||||
s.user_id, u.username, u.email, u.user_level, u.roles, s.expires_at,
|
||||
u.program_user_id, u.program_user_table
|
||||
INTO
|
||||
v_user_id, v_username, v_email, v_user_level, v_roles, v_expires_at
|
||||
v_user_id, v_username, v_email, v_user_level, v_roles, v_expires_at,
|
||||
v_program_user_id, v_program_user_table
|
||||
FROM user_sessions s
|
||||
JOIN users u ON s.user_id = u.id
|
||||
WHERE s.session_token = p_session_token
|
||||
@@ -698,7 +727,9 @@ BEGIN
|
||||
'email', v_email,
|
||||
'user_level', v_user_level,
|
||||
'session_id', p_session_token,
|
||||
'roles', string_to_array(COALESCE(v_roles, ''), ',')
|
||||
'roles', string_to_array(COALESCE(v_roles, ''), ','),
|
||||
'program_user_id', COALESCE(v_program_user_id, 0),
|
||||
'program_user_table', COALESCE(v_program_user_table, '')
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
@@ -815,10 +846,12 @@ DECLARE
|
||||
v_email TEXT;
|
||||
v_user_level INTEGER;
|
||||
v_roles TEXT;
|
||||
v_program_user_id INTEGER;
|
||||
v_program_user_table TEXT;
|
||||
BEGIN
|
||||
-- Query user data
|
||||
SELECT username, email, user_level, roles
|
||||
INTO v_username, v_email, v_user_level, v_roles
|
||||
SELECT username, email, user_level, roles, program_user_id, program_user_table
|
||||
INTO v_username, v_email, v_user_level, v_roles, v_program_user_id, v_program_user_table
|
||||
FROM users
|
||||
WHERE id = p_user_id
|
||||
AND is_active = true;
|
||||
@@ -837,7 +870,9 @@ BEGIN
|
||||
'user_name', v_username,
|
||||
'email', v_email,
|
||||
'user_level', v_user_level,
|
||||
'roles', string_to_array(COALESCE(v_roles, ''), ',')
|
||||
'roles', string_to_array(COALESCE(v_roles, ''), ','),
|
||||
'program_user_id', COALESCE(v_program_user_id, 0),
|
||||
'program_user_table', COALESCE(v_program_user_table, '')
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
@@ -18,6 +18,8 @@ type UserContext struct {
|
||||
Claims map[string]any `json:"claims"`
|
||||
Meta map[string]any `json:"meta"` // Additional metadata that can hold any JSON-serializable values
|
||||
TwoFactorEnabled bool `json:"two_factor_enabled"` // Indicates if 2FA is enabled for this user
|
||||
ProgramUserID int `json:"program_user_id"`
|
||||
ProgramUserTable string `json:"program_user_table"`
|
||||
}
|
||||
|
||||
// LoginRequest contains credentials for login
|
||||
|
||||
@@ -16,6 +16,7 @@ func FromConfigInstanceToServerConfig(sic *config.ServerInstanceConfig, handler
|
||||
Description: sic.Description,
|
||||
Handler: handler,
|
||||
GZIP: sic.GZIP,
|
||||
HTTP2: sic.HTTP2,
|
||||
|
||||
SSLCert: sic.SSLCert,
|
||||
SSLKey: sic.SSLKey,
|
||||
|
||||
@@ -19,6 +19,10 @@ type Config struct {
|
||||
// GZIP compression support
|
||||
GZIP bool
|
||||
|
||||
// HTTP2 enables HTTP/2 with the Extended CONNECT protocol (RFC 8441) for WebSocket support.
|
||||
// Requires TLS; pair with SSLCert/SSLKey, SelfSignedSSL, or AutoTLS.
|
||||
HTTP2 bool
|
||||
|
||||
// TLS/HTTPS configuration options (mutually exclusive)
|
||||
// Option 1: Provide certificate and key files directly
|
||||
SSLCert string
|
||||
|
||||
+32
-8
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
@@ -461,15 +462,38 @@ func newInstance(cfg Config) (*serverInstance, error) {
|
||||
}
|
||||
|
||||
// Create gracefulServer
|
||||
httpServer := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: handler,
|
||||
ReadTimeout: cfg.ReadTimeout,
|
||||
WriteTimeout: cfg.WriteTimeout,
|
||||
IdleTimeout: cfg.IdleTimeout,
|
||||
TLSConfig: tlsConfig,
|
||||
}
|
||||
|
||||
// Enable HTTP/2 with Extended CONNECT (RFC 8441) for WebSocket-over-H2 support.
|
||||
// The GODEBUG=http2xconnect=1 flag is read by net/http's init(); setting it here
|
||||
// ensures it propagates to subprocesses and any future process restarts.
|
||||
// For the current process, set GODEBUG=http2xconnect=1 in the environment before launch.
|
||||
if cfg.HTTP2 {
|
||||
if existing := os.Getenv("GODEBUG"); !strings.Contains(existing, "http2xconnect=1") {
|
||||
if existing == "" {
|
||||
os.Setenv("GODEBUG", "http2xconnect=1")
|
||||
} else {
|
||||
os.Setenv("GODEBUG", existing+",http2xconnect=1")
|
||||
}
|
||||
}
|
||||
if httpServer.HTTP2 == nil {
|
||||
httpServer.HTTP2 = &http.HTTP2Config{}
|
||||
}
|
||||
httpServer.Protocols.SetHTTP2(true)
|
||||
httpServer.Protocols.SetUnencryptedHTTP2(true)
|
||||
} else {
|
||||
httpServer.Protocols.SetHTTP2(false)
|
||||
}
|
||||
|
||||
gracefulSrv := &gracefulServer{
|
||||
server: &http.Server{
|
||||
Addr: addr,
|
||||
Handler: handler,
|
||||
ReadTimeout: cfg.ReadTimeout,
|
||||
WriteTimeout: cfg.WriteTimeout,
|
||||
IdleTimeout: cfg.IdleTimeout,
|
||||
TLSConfig: tlsConfig,
|
||||
},
|
||||
server: httpServer,
|
||||
shutdownTimeout: cfg.ShutdownTimeout,
|
||||
drainTimeout: cfg.DrainTimeout,
|
||||
shutdownComplete: make(chan struct{}),
|
||||
|
||||
Reference in New Issue
Block a user