mlmPenguin commited on
Commit
6fdf9a1
Β·
verified Β·
1 Parent(s): 8a1de17

Upload 6 files

Browse files
SecretSwap/dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ # Secret Swap – Docker image
4
+ # Uses lightweight Alpine Node base
5
+
6
+ FROM node:20-alpine
7
+
8
+ # Create app directory
9
+ WORKDIR /app
10
+
11
+ # Install production dependencies
12
+ COPY package*.json ./
13
+ RUN npm ci --omit=dev
14
+
15
+ # Copy source
16
+ COPY . .
17
+
18
+ # Hugging Face exposes its own $PORT; default to 7860 for local runs
19
+ ENV PORT=${PORT:-7860}
20
+ EXPOSE 7860
21
+
22
+ # Launch the server
23
+ CMD ["node", "server.js"]
SecretSwap/exchanges.json ADDED
File without changes
SecretSwap/package.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "secret-swap",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Ultra‑light secret exchange web app",
6
+ "main": "server.js",
7
+ "scripts": {
8
+ "start": "node server.js"
9
+ },
10
+ "dependencies": {
11
+ "express": "^4.18.2"
12
+ }
13
+ }
SecretSwap/public/style.css ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ /* ===== Secret Swap minimalist styling ===== */
4
+
5
+ *,
6
+ *::before,
7
+ *::after {
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ body {
12
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
13
+ Helvetica, Arial, sans-serif;
14
+ margin: 2rem;
15
+ background: #f9f9fb;
16
+ color: #333;
17
+ line-height: 1.5;
18
+ }
19
+
20
+ h1,
21
+ h2 {
22
+ margin-top: 0;
23
+ color: #111;
24
+ }
25
+
26
+ .container {
27
+ max-width: 640px;
28
+ margin: 0 auto;
29
+ }
30
+
31
+ form {
32
+ margin-top: 1rem;
33
+ }
34
+
35
+ label {
36
+ display: block;
37
+ margin-bottom: 0.75rem;
38
+ font-weight: 600;
39
+ }
40
+
41
+ input[type="text"],
42
+ input[type="password"] {
43
+ width: 100%;
44
+ padding: 0.45rem 0.6rem;
45
+ border: 1px solid #ccc;
46
+ border-radius: 4px;
47
+ font-size: 1rem;
48
+ }
49
+
50
+ button {
51
+ padding: 0.55rem 1.1rem;
52
+ border: none;
53
+ border-radius: 4px;
54
+ background: #1e88e5;
55
+ color: #fff;
56
+ font-size: 1rem;
57
+ cursor: pointer;
58
+ transition: background 0.18s ease-in-out;
59
+ }
60
+
61
+ button:hover {
62
+ background: #1565c0;
63
+ }
64
+
65
+ a {
66
+ color: #1e88e5;
67
+ text-decoration: none;
68
+ }
69
+
70
+ a:hover {
71
+ text-decoration: underline;
72
+ }
73
+
74
+ ul {
75
+ padding-left: 1.25rem;
76
+ }
77
+
78
+ li {
79
+ margin-bottom: 0.35rem;
80
+ }
81
+
82
+ .message {
83
+ padding: 0.85rem 1rem;
84
+ border: 1px solid #1e88e5;
85
+ background: #e3f2fd;
86
+ border-radius: 4px;
87
+ margin: 1rem 0;
88
+ }
89
+
90
+ .error {
91
+ border-color: #e53935;
92
+ background: #ffebee;
93
+ }
SecretSwap/server.js ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Secret-Swap – minimal Express server with flat-file storage */
2
+ import express from 'express';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import crypto from 'crypto';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ const USERS_FILE = path.join(__dirname, 'users.json');
12
+ const EXCH_FILE = path.join(__dirname, 'exchanges.json');
13
+ const PORT = process.env.PORT || 3000;
14
+
15
+ /* ---------- helpers ---------- */
16
+ const readJSON = (f, d = {}) => (fs.existsSync(f) ? JSON.parse(fs.readFileSync(f)) : d);
17
+ const writeJSON = (f, o) => fs.writeFileSync(f, JSON.stringify(o, null, 2));
18
+
19
+ const sessions = new Map(); // token β†’ username
20
+ const genToken = () => crypto.randomUUID();
21
+
22
+ function hashPass(pw, salt = crypto.randomBytes(16).toString('hex')) {
23
+ const hash = crypto.scryptSync(pw, salt, 64).toString('hex');
24
+ return `${salt}:${hash}`;
25
+ }
26
+ function checkPass(pw, stored) {
27
+ const [salt, ref] = stored.split(':');
28
+ const hash = crypto.scryptSync(pw, salt, 64).toString('hex');
29
+ return crypto.timingSafeEqual(Buffer.from(hash, 'hex'), Buffer.from(ref, 'hex'));
30
+ }
31
+
32
+ /* ---------- tiny templating ---------- */
33
+ const page = (title, body) => `<!DOCTYPE html><html><head><meta charset=utf-8><title>${title}</title><link rel="stylesheet" href="/style.css"></head><body><h1>${title}</h1>${body}</body></html>`;
34
+ const input = (n,l,t='text') => `<label>${l}: <input type="${t}" name="${n}" required></label><br>`;
35
+
36
+ /* ---------- app ---------- */
37
+ const app = express();
38
+ app.use(express.urlencoded({extended:true}));
39
+ app.use(express.static(path.join(__dirname,'public')));
40
+
41
+ app.use((req, _res, next) => {
42
+ const token = (req.headers.cookie||'').split(';').map(c=>c.trim().split('='))[0]?.[1];
43
+ req.user = sessions.get(token);
44
+ req.token = token;
45
+ next();
46
+ });
47
+ const needAuth = (req,res,next)=>req.user?next():res.redirect('/login');
48
+
49
+ /* ---------- auth ---------- */
50
+ app.get('/register', (req,res)=>res.send(page('Register',`
51
+ <form method=post action=/register>
52
+ ${input('username','Username')}
53
+ ${input('password','Password','password')}
54
+ <button>Register</button>
55
+ </form>
56
+ <p><a href=/login>Have an account? Login</a></p>`)));
57
+
58
+ app.post('/register',(req,res)=>{
59
+ const {username,password}=req.body;
60
+ const users=readJSON(USERS_FILE,{});
61
+ if(users[username])return res.send(page('Error','<p>User exists.</p>'));
62
+ users[username]=hashPass(password);
63
+ writeJSON(USERS_FILE,users);
64
+ res.redirect('/login');
65
+ });
66
+
67
+ app.get('/login',(req,res)=>res.send(page('Login',`
68
+ <form method=post action=/login>
69
+ ${input('username','Username')}
70
+ ${input('password','Password','password')}
71
+ <button>Login</button>
72
+ </form>
73
+ <p><a href=/register>No account? Register</a></p>`)));
74
+
75
+ app.post('/login',(req,res)=>{
76
+ const {username,password}=req.body;
77
+ const users=readJSON(USERS_FILE,{});
78
+ if(!users[username]||!checkPass(password,users[username]))
79
+ return res.send(page('Error','<p>Bad credentials.</p>'));
80
+ const token=genToken();
81
+ sessions.set(token,username);
82
+ res.setHeader('Set-Cookie',`token=${token}; HttpOnly; Path=/`);
83
+ res.redirect('/dashboard');
84
+ });
85
+
86
+ app.get('/logout',(req,res)=>{
87
+ if(req.token) sessions.delete(req.token);
88
+ res.setHeader('Set-Cookie','token=; Max-Age=0; Path=/');
89
+ res.redirect('/login');
90
+ });
91
+
92
+ /* ---------- dashboard ---------- */
93
+ app.get(['/','/dashboard'],needAuth,(req,res)=>{
94
+ const exchanges=readJSON(EXCH_FILE,{});
95
+ const list=Object.entries(exchanges)
96
+ .filter(([id,x])=>x.owner===req.user||x.partner===req.user)
97
+ .map(([id,x])=>{
98
+ const role = x.owner===req.user? 'owner':'partner';
99
+ return `<li>[${role}] β€œ${x.secret}” β†’ <a href=/respond/${id}>link</a>${x.responses.length?` (${x.responses.length} reply)`:' '}</li>`;
100
+ }).join('');
101
+ res.send(page('Dashboard',`
102
+ <p>Logged in as <strong>${req.user}</strong> | <a href=/logout>Logout</a></p>
103
+ <h2>Create new secret swap</h2>
104
+ <form method=post action=/create>
105
+ ${input('partner','Partner username')}
106
+ ${input('secret','Your secret')}
107
+ <button>Create & share</button>
108
+ </form>
109
+ <h2>Your swaps</h2>
110
+ <ul>${list||'<li>(none yet)</li>'}</ul>`));
111
+ });
112
+
113
+ /* ---------- create ---------- */
114
+ app.post('/create',needAuth,(req,res)=>{
115
+ const {secret,partner}=req.body;
116
+ const users=readJSON(USERS_FILE,{});
117
+ if(!users[partner]) return res.send(page('Error','<p>Partner username not found.</p>'));
118
+ const exchanges=readJSON(EXCH_FILE,{});
119
+ const id=crypto.randomUUID();
120
+ exchanges[id]={id,owner:req.user,partner,secret,responses:[]};
121
+ writeJSON(EXCH_FILE,exchanges);
122
+ const host=req.headers.host;
123
+ res.send(page('Swap created',`
124
+ <p>Send this link to <strong>${partner}</strong>:</p>
125
+ <p><a href=/respond/${id}>http://${host}/respond/${id}</a></p>
126
+ <p><a href=/dashboard>Return to dashboard</a></p>`));
127
+ });
128
+
129
+ /* ---------- respond ---------- */
130
+ app.get('/respond/:id',needAuth,(req,res)=>{
131
+ const ex=readJSON(EXCH_FILE,{})[req.params.id];
132
+ if(!ex) return res.send(page('Error','<p>Swap not found.</p>'));
133
+ if(req.user!==ex.partner && req.user!==ex.owner)
134
+ return res.send(page('Forbidden','<p>You are not part of this swap.</p>'));
135
+ if(req.user===ex.owner)
136
+ return res.redirect(`/view/${req.params.id}`);
137
+ const done=ex.responses.some(r=>r.from===req.user);
138
+ if(done) return res.redirect(`/view/${req.params.id}`);
139
+ res.send(page('Respond to secret',
140
+ `<p>Original secret will be revealed after you submit yours.</p>
141
+ <form method=post action=/respond/${req.params.id}>
142
+ ${input('response','Your secret')}
143
+ <button>Submit</button>
144
+ </form>`));
145
+ });
146
+
147
+ app.post('/respond/:id',needAuth,(req,res)=>{
148
+ const data=readJSON(EXCH_FILE,{});
149
+ const ex=data[req.params.id];
150
+ if(!ex||req.user!==ex.partner) return res.send(page('Error','<p>Not allowed.</p>'));
151
+ ex.responses.push({from:req.user,secret:req.body.response});
152
+ writeJSON(EXCH_FILE,data);
153
+ res.send(page('Secret revealed',`<p>Original secret from ${ex.owner}: <strong>${ex.secret}</strong></p>
154
+ <p><a href=/dashboard>Back to dashboard</a></p>`));
155
+ });
156
+
157
+ /* ---------- view (owner) ---------- */
158
+ app.get('/view/:id',needAuth,(req,res)=>{
159
+ const ex=readJSON(EXCH_FILE,{})[req.params.id];
160
+ if(!ex||req.user!==ex.owner) return res.send(page('Error','<p>Not allowed.</p>'));
161
+ const list=ex.responses.map(r=>`<li>${r.from}: ${r.secret}</li>`).join('')||'<li>(no response yet)</li>';
162
+ res.send(page('Swap responses',`
163
+ <p>Your secret: <strong>${ex.secret}</strong></p>
164
+ <ul>${list}</ul>
165
+ <p><a href=/dashboard>Back to dashboard</a></p>`));
166
+ });
167
+
168
+ /* ---------- start ---------- */
169
+ app.listen(PORT,()=>console.log(`Secret-Swap listening on ${PORT}`));
SecretSwap/users.json ADDED
File without changes