-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathseed_data.py
More file actions
279 lines (232 loc) · 10.5 KB
/
seed_data.py
File metadata and controls
279 lines (232 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
"""
Database initialization and seed data for Code Dojo.
Run this script to create the database and populate it with:
- Sample learning module and goals
- Admin and instructor users
- Sample student users
Usage:
python seed_data.py
python seed_data.py --reset # Drop and recreate all tables
"""
import sys
from app import app, db
from models.user import User
from models.module import LearningModule
from models.goal import LearningGoal
from models.anatomy_topic import AnatomyTopic
# Challenge description for the API Auth challenge
API_AUTH_CHALLENGE = """
## Challenge: Add API Authentication
The Snippet Manager API currently allows **anyone** to create, update, and delete code snippets without any authentication. This is a security vulnerability!
### Your Task
Add authentication to protect write operations (POST, PUT, DELETE) while keeping read operations (GET) public.
### Requirements
1. **Choose an approach** - You can use either:
- **API Key Authentication** - Using an `X-API-Key` header
- **HTTP Basic Authentication** - Using `Authorization: Basic` header
2. **Protect write operations**:
- POST `/api/snippets` (create)
- PUT `/api/snippets/<id>` (update)
- DELETE `/api/snippets/<id>` (delete)
3. **Keep read operations public**:
- GET `/api/snippets` (list all)
- GET `/api/snippets/<id>` (get one)
- GET `/api/languages`
- GET `/api/tags`
4. **Return proper error responses**:
- 401 Unauthorized when credentials are missing or invalid
- Include helpful error message
### Getting Started
1. Fork or clone the starter repository
2. Implement your authentication solution
3. Update the tests to verify auth works
4. Submit your solution branch
### Hints
- Look at how Flask decorators work
- Consider using `functools.wraps` for your decorator
- For API keys, `secrets.token_hex(32)` generates secure random keys
- For Basic Auth, werkzeug has password hashing utilities
### Success Criteria
- All existing tests still pass (read operations work without auth)
- New tests verify auth is required for write operations
- Invalid credentials return 401 with helpful message
- Valid credentials allow write operations
"""
def seed_database():
"""Create tables and insert sample data."""
print("Creating database tables...")
db.create_all()
# Check if data already exists
if LearningModule.query.first():
print("Database already has data. Use --reset to recreate.")
return
print("Creating learning module...")
# Create the Flask API Auth module
module = LearningModule(
title="Flask API Authentication",
description="Learn how to secure your Flask REST APIs with authentication. You'll implement authentication from scratch and understand the security principles behind it.",
order=1
)
db.session.add(module)
db.session.flush() # Get the module ID
# Create the learning goal
goal = LearningGoal(
module_id=module.id,
title="Add API Key Authentication",
video_url="https://www.youtube.com/watch?v=o-pMCoVPN_k",
challenge_md=API_AUTH_CHALLENGE,
starter_repo="https://github.com/nsuberi/snippet-manager-starter",
order=1
)
db.session.add(goal)
db.session.flush() # Get the goal ID
print("Creating anatomy topics...")
# Create anatomy topics for the API Auth challenge
anatomy_topics = [
AnatomyTopic(
goal_id=goal.id,
name="Authentication Decorator",
description="The decorator pattern you used to protect routes - how it intercepts requests and validates credentials before allowing access.",
suggested_analogies="A decorator is like a security guard at a building entrance. Before anyone can enter (access the function), the guard checks their ID (validates credentials). If they pass, they're allowed in; if not, they're turned away.",
order=1
),
AnatomyTopic(
goal_id=goal.id,
name="API Key Validation",
description="How your code checks if the provided API key is valid and matches expected credentials.",
suggested_analogies="Think of API keys like a special password or VIP pass. Just like a bouncer checks if your name is on the guest list, your code checks if the provided key matches one that's been registered.",
order=2
),
AnatomyTopic(
goal_id=goal.id,
name="HTTP Headers",
description="How you extract and use the X-API-Key header from incoming requests.",
suggested_analogies="HTTP headers are like the envelope of a letter - they contain metadata about the message (who it's from, special handling instructions) separate from the actual content inside.",
order=3
),
AnatomyTopic(
goal_id=goal.id,
name="Error Responses",
description="How your code returns appropriate 401 Unauthorized responses when authentication fails.",
suggested_analogies="Error responses are like a helpful receptionist who doesn't just say 'no' but explains why you can't proceed and what you might need to do differently.",
order=4
),
AnatomyTopic(
goal_id=goal.id,
name="Route Protection Strategy",
description="Your approach to deciding which routes need protection (write operations) vs which stay public (read operations).",
suggested_analogies="This is like a museum where anyone can look at the exhibits (read), but only authorized staff can move or modify them (write). You're deciding what requires a staff badge.",
order=5
),
]
for topic in anatomy_topics:
db.session.add(topic)
print("Creating users...")
# Create admin user
admin = User.create(
email="admin@codedojo.com",
password="admin123",
role="admin"
)
print(f" Admin: admin@codedojo.com / admin123")
# Create instructor user
instructor = User.create(
email="instructor@codedojo.com",
password="instructor123",
role="instructor"
)
print(f" Instructor: instructor@codedojo.com / instructor123")
# Create sample student users
student1 = User.create(
email="alice@example.com",
password="student123",
role="student"
)
print(f" Student 1: alice@example.com / student123")
student2 = User.create(
email="bob@example.com",
password="student123",
role="student"
)
print(f" Student 2: bob@example.com / student123")
db.session.commit()
print("\n" + "=" * 60)
print("SEED DATA CREATED SUCCESSFULLY")
print("=" * 60)
print("\nTest Credentials:")
print(" Admin: admin@codedojo.com / admin123")
print(" Instructor: instructor@codedojo.com / instructor123")
print(" Student 1: alice@example.com / student123")
print(" Student 2: bob@example.com / student123")
print("\nLearning Content:")
print(f" Module: {module.title}")
print(f" Goal: {goal.title}")
print(f" Starter Repo: {goal.starter_repo}")
print("=" * 60)
def reset_database():
"""Drop all tables and recreate with seed data."""
print("Dropping all tables...")
db.drop_all()
seed_database()
def seed_anatomy_topics():
"""Add anatomy topics to existing goals (without resetting database)."""
print("Seeding anatomy topics...")
# Check if topics already exist
if AnatomyTopic.query.first():
print("Anatomy topics already exist. Skipping.")
return
# Get the first goal (API Auth challenge)
goal = LearningGoal.query.first()
if not goal:
print("No learning goals found. Run seed_database() first.")
return
print(f"Adding anatomy topics to goal: {goal.title}")
anatomy_topics = [
AnatomyTopic(
goal_id=goal.id,
name="Authentication Decorator",
description="The decorator pattern you used to protect routes - how it intercepts requests and validates credentials before allowing access.",
suggested_analogies="A decorator is like a security guard at a building entrance. Before anyone can enter (access the function), the guard checks their ID (validates credentials). If they pass, they're allowed in; if not, they're turned away.",
order=1
),
AnatomyTopic(
goal_id=goal.id,
name="API Key Validation",
description="How your code checks if the provided API key is valid and matches expected credentials.",
suggested_analogies="Think of API keys like a special password or VIP pass. Just like a bouncer checks if your name is on the guest list, your code checks if the provided key matches one that's been registered.",
order=2
),
AnatomyTopic(
goal_id=goal.id,
name="HTTP Headers",
description="How you extract and use the X-API-Key header from incoming requests.",
suggested_analogies="HTTP headers are like the envelope of a letter - they contain metadata about the message (who it's from, special handling instructions) separate from the actual content inside.",
order=3
),
AnatomyTopic(
goal_id=goal.id,
name="Error Responses",
description="How your code returns appropriate 401 Unauthorized responses when authentication fails.",
suggested_analogies="Error responses are like a helpful receptionist who doesn't just say 'no' but explains why you can't proceed and what you might need to do differently.",
order=4
),
AnatomyTopic(
goal_id=goal.id,
name="Route Protection Strategy",
description="Your approach to deciding which routes need protection (write operations) vs which stay public (read operations).",
suggested_analogies="This is like a museum where anyone can look at the exhibits (read), but only authorized staff can move or modify them (write). You're deciding what requires a staff badge.",
order=5
),
]
for topic in anatomy_topics:
db.session.add(topic)
db.session.commit()
print(f"Created {len(anatomy_topics)} anatomy topics successfully!")
if __name__ == '__main__':
with app.app_context():
if len(sys.argv) > 1 and sys.argv[1] == '--reset':
reset_database()
elif len(sys.argv) > 1 and sys.argv[1] == '--anatomy':
seed_anatomy_topics()
else:
seed_database()