Skip to content

Commit aba7de8

Browse files
authored
Merge pull request #160 from truthixify/fix-149
Added Missing Security Measures and Access Control
2 parents bb46b4c + 8eeacac commit aba7de8

8 files changed

Lines changed: 597 additions & 29 deletions

File tree

.github/workflows/contracts-ci.yml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ jobs:
2020
- name: Install asdf
2121
uses: asdf-vm/actions/setup@v2
2222

23-
- name: Install plugins
23+
- name: Install Dojo using dojoup
24+
run: |
25+
curl -L https://install.dojoengine.org | bash
26+
. "$HOME/.config/.dojo/env"
27+
dojoup install 1.5.0
28+
echo "$HOME/.config/.dojo/bin" >> $GITHUB_PATH
29+
shell: bash
30+
31+
- name: Install Starknet Foundry
2432
run: |
25-
asdf plugin add scarb
26-
asdf install scarb 2.10.1
27-
asdf global scarb 2.10.1
28-
asdf plugin add dojo https://github.com/dojoengine/asdf-dojo
29-
asdf install dojo 1.5.0
30-
asdf global dojo 1.5.0
3133
asdf plugin add starknet-foundry
3234
asdf install starknet-foundry 0.35.0
3335
asdf global starknet-foundry 0.35.0

src/helpers/security.cairo

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
use starknet::{ContractAddress, get_caller_address, get_block_timestamp};
2+
use dojo::model::ModelStorage;
3+
use dojo::event::EventStorage;
4+
use dojo::world::WorldStorage;
5+
use coa::models::core::Contract;
6+
use coa::models::security::{
7+
RateLimit, SecurityConfig, AdminRole, PlayerSecurityStatus, SecurityEvent, RateLimitExceeded,
8+
ContractPaused, ContractUnpaused,
9+
};
10+
use coa::models::session::SessionKey;
11+
use core::num::traits::Zero;
12+
use core::poseidon::poseidon_hash_span;
13+
14+
// Security constants
15+
pub const SUPER_ADMIN: felt252 = 'SUPER_ADMIN';
16+
pub const GAME_ADMIN: felt252 = 'GAME_ADMIN';
17+
pub const MODERATOR: felt252 = 'MODERATOR';
18+
19+
// Operation types as numbers for rate limiting
20+
pub const CREATE_SESSION_OP: u32 = 1;
21+
pub const SPAWN_ITEMS_OP: u32 = 2;
22+
pub const ADMIN_ACTION_OP: u32 = 3;
23+
24+
pub const SECURITY_CONFIG_ID: felt252 = 'SECURITY_CONFIG';
25+
pub const COA_CONTRACTS: felt252 = 'COA_CONTRACTS';
26+
27+
// Basic admin validation function (simplified)
28+
pub fn validate_admin_access(world: WorldStorage, _required_role: felt252) {
29+
let caller = get_caller_address();
30+
31+
// Validate caller is not zero address
32+
assert(!caller.is_zero(), 'ZERO_ADDRESS');
33+
34+
// Read contract state
35+
let contract: Contract = world.read_model(COA_CONTRACTS);
36+
37+
// Validate contract state
38+
assert(!contract.admin.is_zero(), 'INVALID_CONTRACT_STATE');
39+
assert(!contract.paused, 'CONTRACT_PAUSED');
40+
41+
// Basic admin check (simplified for now)
42+
assert(caller == contract.admin, 'INSUFFICIENT_PERMISSIONS');
43+
}
44+
45+
// Full admin validation function
46+
pub fn validate_admin_access_full(world: WorldStorage, required_role: felt252) {
47+
let caller = get_caller_address();
48+
49+
// Validate caller is not zero address
50+
assert(!caller.is_zero(), 'ZERO_ADDRESS');
51+
52+
// Read contract state
53+
let contract: Contract = world.read_model(COA_CONTRACTS);
54+
55+
// Validate contract state
56+
assert(!contract.admin.is_zero(), 'INVALID_CONTRACT_STATE');
57+
assert(!contract.paused, 'CONTRACT_PAUSED');
58+
59+
// Check if caller is super admin (contract admin)
60+
if caller == contract.admin {
61+
return;
62+
}
63+
64+
// Check role-based access
65+
let admin_role: AdminRole = world.read_model(caller);
66+
assert(admin_role.is_active, 'INSUFFICIENT_PERMISSIONS');
67+
68+
// Validate role hierarchy using if-else
69+
if required_role == SUPER_ADMIN {
70+
assert(caller == contract.admin, 'SUPER_ADMIN_REQUIRED');
71+
} else if required_role == GAME_ADMIN {
72+
assert(
73+
caller == contract.admin
74+
|| admin_role.role_type == GAME_ADMIN
75+
|| admin_role.role_type == SUPER_ADMIN,
76+
'GAME_ADMIN_REQUIRED',
77+
);
78+
} else if required_role == MODERATOR {
79+
assert(
80+
caller == contract.admin
81+
|| admin_role.role_type == SUPER_ADMIN
82+
|| admin_role.role_type == GAME_ADMIN
83+
|| admin_role.role_type == MODERATOR,
84+
'MODERATOR_REQUIRED',
85+
);
86+
} else {
87+
assert(false, 'INVALID_ROLE');
88+
}
89+
}
90+
91+
pub fn validate_player_access(world: WorldStorage, player_id: ContractAddress) {
92+
let caller = get_caller_address();
93+
94+
// Validate caller is not zero address
95+
assert(!caller.is_zero(), 'ZERO_ADDRESS');
96+
97+
// Validate player ID matches caller (unless admin)
98+
let contract: Contract = world.read_model(COA_CONTRACTS);
99+
if caller != contract.admin {
100+
assert(caller == player_id, 'UNAUTHORIZED_PLAYER');
101+
}
102+
103+
// Check if player is banned
104+
let security_status: PlayerSecurityStatus = world.read_model(player_id);
105+
if security_status.is_banned {
106+
let current_time = get_block_timestamp();
107+
if security_status.ban_expires_at > current_time {
108+
assert(false, 'PLAYER_BANNED');
109+
}
110+
}
111+
}
112+
113+
pub fn check_rate_limit(
114+
mut world: WorldStorage, user: ContractAddress, operation_type: u32,
115+
) -> bool {
116+
let current_time = get_block_timestamp();
117+
let time_window = current_time / 3600; // 1 hour windows
118+
let operation_felt: felt252 = operation_type.into();
119+
let rate_limit_key = (user, operation_felt, time_window);
120+
121+
let mut rate_limit: RateLimit = world.read_model(rate_limit_key);
122+
let security_config: SecurityConfig = world.read_model(SECURITY_CONFIG_ID);
123+
124+
// Use if-else for operation type checking
125+
let limit = if operation_type == CREATE_SESSION_OP {
126+
security_config.max_sessions_per_hour
127+
} else if operation_type == SPAWN_ITEMS_OP {
128+
security_config.max_spawns_per_hour
129+
} else if operation_type == ADMIN_ACTION_OP {
130+
50 // Default admin action limit
131+
} else {
132+
100 // Default limit
133+
};
134+
135+
if rate_limit.count >= limit {
136+
// Emit rate limit exceeded event
137+
let event = RateLimitExceeded {
138+
user,
139+
operation: operation_felt,
140+
current_count: rate_limit.count,
141+
limit,
142+
timestamp: current_time,
143+
};
144+
world.emit_event(@event);
145+
146+
// Log security event
147+
let security_event = SecurityEvent {
148+
event_type: 'RATE_LIMIT_EXCEEDED',
149+
user,
150+
timestamp: current_time,
151+
details: operation_felt,
152+
};
153+
world.emit_event(@security_event);
154+
155+
return false;
156+
}
157+
158+
rate_limit.count += 1;
159+
world.write_model(@rate_limit);
160+
true
161+
}
162+
163+
pub fn sanitize_input(input: felt252) -> felt252 {
164+
// Basic input sanitization - in a real implementation,
165+
// you might want to check for malicious patterns
166+
input
167+
}
168+
169+
pub fn validate_faction(faction: felt252) -> bool {
170+
faction == 'CHAOS_MERCENARIES' || faction == 'SUPREME_LAW' || faction == 'REBEL_TECHNOMANCERS'
171+
}
172+
173+
pub fn validate_session_duration(duration: u64) -> bool {
174+
duration >= 3600 && duration <= 86400 // 1 hour to 24 hours
175+
}
176+
177+
pub fn generate_secure_session_id(player: ContractAddress) -> felt252 {
178+
// Use multiple sources of entropy for security
179+
let tx_hash: felt252 = starknet::get_tx_info().unbox().transaction_hash;
180+
let block_timestamp: felt252 = get_block_timestamp().into();
181+
let player_address: felt252 = player.into();
182+
183+
// Combine entropy sources
184+
let mut hash_data = array![tx_hash, block_timestamp, player_address];
185+
poseidon_hash_span(hash_data.span())
186+
}
187+
188+
pub fn validate_contract_not_paused(world: WorldStorage) {
189+
let contract: Contract = world.read_model(COA_CONTRACTS);
190+
assert(!contract.paused, 'CONTRACT_PAUSED');
191+
}
192+
193+
pub fn log_security_event(
194+
mut world: WorldStorage, event_type: felt252, user: ContractAddress, details: felt252,
195+
) {
196+
let event = SecurityEvent { event_type, user, timestamp: get_block_timestamp(), details };
197+
world.emit_event(@event);
198+
}
199+
200+
// Emergency functions
201+
pub fn pause_contract(mut world: WorldStorage, reason: felt252) {
202+
validate_admin_access(world, SUPER_ADMIN);
203+
204+
let mut contract: Contract = world.read_model(COA_CONTRACTS);
205+
contract.paused = true;
206+
world.write_model(@contract);
207+
208+
let event = ContractPaused {
209+
paused_by: get_caller_address(), timestamp: get_block_timestamp(), reason,
210+
};
211+
world.emit_event(@event);
212+
}
213+
214+
pub fn unpause_contract(mut world: WorldStorage) {
215+
validate_admin_access(world, SUPER_ADMIN);
216+
217+
let mut contract: Contract = world.read_model(COA_CONTRACTS);
218+
contract.paused = false;
219+
world.write_model(@contract);
220+
221+
let event = ContractUnpaused {
222+
unpaused_by: get_caller_address(), timestamp: get_block_timestamp(),
223+
};
224+
world.emit_event(@event);
225+
}
226+
227+
// Session security helpers
228+
pub fn create_secure_session(
229+
mut world: WorldStorage, session_duration: u64, max_transactions: u32,
230+
) -> felt252 {
231+
let caller = get_caller_address();
232+
let current_time = get_block_timestamp();
233+
234+
// Validate inputs
235+
assert(validate_session_duration(session_duration), 'INVALID_DURATION');
236+
assert(max_transactions > 0 && max_transactions <= 1000, 'INVALID_TRANSACTIONS');
237+
238+
// Check rate limiting
239+
assert(check_rate_limit(world, caller, CREATE_SESSION_OP), 'RATE_LIMIT_EXCEEDED');
240+
241+
// Generate secure session ID
242+
let session_id = generate_secure_session_id(caller);
243+
244+
// Create session with security measures
245+
let session_key = SessionKey {
246+
session_id,
247+
player_address: caller,
248+
session_key_address: caller,
249+
created_at: current_time,
250+
expires_at: current_time + session_duration,
251+
last_used: current_time,
252+
status: 0, // Active
253+
max_transactions,
254+
used_transactions: 0,
255+
is_valid: true,
256+
};
257+
258+
world.write_model(@session_key);
259+
session_id
260+
}
261+
262+
// Input validation helpers
263+
pub fn validate_item_id(item_id: u256) -> bool {
264+
item_id > 0
265+
}
266+
267+
pub fn validate_quantity(quantity: u256) -> bool {
268+
quantity > 0 && quantity <= 1000000 // Reasonable upper limit
269+
}
270+
271+
pub fn validate_address_not_zero(address: ContractAddress) -> bool {
272+
!address.is_zero()
273+
}

src/lib.cairo

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub mod models {
3030
pub mod pet_stats;
3131
pub mod tournament;
3232
pub mod session;
33+
pub mod security;
3334
pub mod weapon {
3435
pub mod blunt;
3536
pub mod bow;
@@ -65,6 +66,7 @@ pub mod helpers {
6566
pub mod gear;
6667
pub mod body;
6768
pub mod session_validation;
69+
pub mod security;
6870
}
6971

7072
pub mod types {
@@ -87,6 +89,7 @@ pub mod test {
8789
pub mod upgrade_gear_test;
8890
pub mod gear_read_test;
8991
pub mod model_test_player;
92+
pub mod security_test;
9093
}
9194

9295
pub mod traits {

0 commit comments

Comments
 (0)