Skip to content

Commit eb399aa

Browse files
authored
Merge pull request #28 from coderdojo-linz/feature/workshop-authentication
Feature/workshop authentication
2 parents 2b465af + 074309b commit eb399aa

29 files changed

+859
-190
lines changed

workshop-ui/public/apple-icon.png

6.02 KB
Loading

workshop-ui/public/favicon.ico

14.7 KB
Binary file not shown.

workshop-ui/public/icon0.svg

Lines changed: 3 additions & 0 deletions
Loading

workshop-ui/public/icon1.png

3.84 KB
Loading

workshop-ui/public/manifest.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "CoderDojo AI Workshops",
3+
"short_name": "CD AI",
4+
"icons": [
5+
{
6+
"src": "/web-app-manifest-192x192.png",
7+
"sizes": "192x192",
8+
"type": "image/png",
9+
"purpose": "maskable"
10+
},
11+
{
12+
"src": "/web-app-manifest-512x512.png",
13+
"sizes": "512x512",
14+
"type": "image/png",
15+
"purpose": "maskable"
16+
}
17+
],
18+
"theme_color": "#ffffff",
19+
"background_color": "#ffffff",
20+
"display": "standalone"
21+
}
7.74 KB
Loading
26 KB
Loading
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { getAppSessionFromRequest, validateAppSession } from "@/lib/session"
2+
import { NextRequest, NextResponse } from "next/server"
3+
4+
/**
5+
* @route GET /api/auth/me
6+
* @desc Get current authentication status
7+
*
8+
* @access Public
9+
*/
10+
export async function GET(request: NextRequest) {
11+
// Check current authentication status
12+
const response = NextResponse.json({ authenticated: false })
13+
14+
try {
15+
const session = await getAppSessionFromRequest(request, response)
16+
17+
if (await validateAppSession(session)) {
18+
return NextResponse.json({
19+
authenticated: true,
20+
workshopName: session.workshopName,
21+
})
22+
} else {
23+
return NextResponse.json({}, { status: 401 })
24+
}
25+
} catch (error) {
26+
console.error('Session check error:', error)
27+
return NextResponse.json({}, { status: 401 })
28+
}
29+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { getAppSessionFromRequest, validateAccessCode, validateAppSession } from '@/lib/session'
3+
import { readWorkshops } from '@/lib/workshopService'
4+
import { Workshop } from '@/lib/workshop-schema'
5+
6+
/**
7+
* @route POST /api/auth
8+
* @desc Handle login and logout actions
9+
* @body { action: 'login' | 'logout', code?: string }
10+
* @response 200 { success: boolean } or 400/401 { error: string }
11+
* @access Public
12+
*/
13+
export async function POST(request: NextRequest) {
14+
try {
15+
const body = await request.json()
16+
const { action, code } = body
17+
const response = NextResponse.json({ success: true })
18+
19+
if (action === 'login') {
20+
if (await validateAccessCode(code)) {
21+
// Set session variables
22+
const session = await getAppSessionFromRequest(request, response)
23+
session.accessCode = code
24+
session.isAuthenticated = true
25+
session.recheckCode = new Date(Date.now() + 1000 * 60 * 30) // recheck code in 30 minutes
26+
27+
// Find a workshop associated with this access code
28+
const workshops = await readWorkshops()
29+
const workshop = workshops.find((w: Workshop) => w.code === code)
30+
if (workshop) {
31+
session.workshopName = workshop.title
32+
} else {
33+
session.workshopName = 'Unknown Workshop'
34+
}
35+
console.log(`User logged in to workshop: ${JSON.stringify(session)}`)
36+
await session.save()
37+
38+
return response
39+
} else {
40+
// report invalid code to client
41+
return NextResponse.json(
42+
{ error: 'Invalid access code' },
43+
{ status: 401 }
44+
)
45+
}
46+
} else if (action === 'logout') {
47+
// Clear the session
48+
const session = await getAppSessionFromRequest(request, response)
49+
session.destroy()
50+
51+
return response
52+
}
53+
54+
return NextResponse.json(
55+
{ error: 'Invalid action' },
56+
{ status: 400 }
57+
)
58+
} catch (error) {
59+
console.error('Auth API error:', error)
60+
return NextResponse.json(
61+
{ error: 'Invalid request' },
62+
{ status: 400 }
63+
)
64+
}
65+
}
66+
67+
/**
68+
* @route GET /api/auth
69+
* @desc Get current authentication status
70+
* @response { authenticated: boolean }
71+
* @access Public
72+
*/
73+
export async function GET(request: NextRequest) {
74+
// Check current authentication status
75+
const response = NextResponse.json({ authenticated: false })
76+
77+
try {
78+
const session = await getAppSessionFromRequest(request, response)
79+
const isAuthenticated = await validateAppSession(session)
80+
81+
return NextResponse.json({
82+
authenticated: isAuthenticated
83+
})
84+
} catch (error) {
85+
console.error('Session check error:', error)
86+
return NextResponse.json({
87+
authenticated: false
88+
})
89+
}
90+
}

workshop-ui/src/app/api/chat/route.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
22
import OpenAI, { AzureOpenAI } from 'openai';
33
import fs from 'fs';
44
import path from 'path';
5-
import { getSession } from '@/lib/session';
5+
import { getAppSessionFromRequest, getChatSession, validateAppSession } from '@/lib/session';
66
import { randomUUID } from 'crypto';
77
import '@/lib/files';
88
import { trace, Span } from '@opentelemetry/api';
@@ -16,8 +16,23 @@ const tracer = trace.getTracer('ai-workshop-chat');
1616
// TODO: Persist this to a database later
1717
const sessionResponseMap = new Map<string, { previousResponseId: string; sessionInstanceId: string }>();
1818

19+
/**
20+
* @route POST /api/chat
21+
* @desc Send a chat message
22+
* @body { message: string, resetConversation: boolean }
23+
* @urlParam exercise: string
24+
* @response 200 { response: string } or 400 { error: string }
25+
* @access Protected (any authenticated user/workshop)
26+
*/
1927
export async function POST(request: NextRequest) {
2028
try {
29+
// Validate authentication
30+
const response = NextResponse.next();
31+
const appSession = await getAppSessionFromRequest(request, response);
32+
if (!await validateAppSession(appSession)) {
33+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
34+
}
35+
2136
// Get body payload and query string parameters
2237
const { message, resetConversation } = await request.json();
2338
if (!message || typeof message !== 'string') {
@@ -36,7 +51,7 @@ export async function POST(request: NextRequest) {
3651
const exerciseData = exerciseResult.value;
3752

3853
// Get or create session ID
39-
const session = await getSession();
54+
const session = await getChatSession();
4055
let sessionId = session.sessionId;
4156

4257
if (!sessionId) {

0 commit comments

Comments
 (0)