my own indieAuth provider! indiko.dunkirk.sh/docs
indieauth oauth2-server
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 325 lines 8.1 kB view raw
1<!doctype html> 2<html lang="en"> 3 4<head> 5 <meta charset="UTF-8" /> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 <title>oauth test client • indiko</title> 8 <link rel="icon" href="../../public/favicon.svg" type="image/svg+xml" /> 9 <link rel="preconnect" href="https://fonts.googleapis.com"> 10 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 11 <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap" rel="stylesheet"> 12 <style> 13 :root { 14 --mahogany: #26242b; 15 --lavender: #d9d0de; 16 --old-rose: #bc8da0; 17 --rosewood: #a04668; 18 --berry-crush: #ab4967; 19 } 20 21 * { 22 margin: 0; 23 padding: 0; 24 box-sizing: border-box; 25 } 26 27 body { 28 font-family: "Space Grotesk", sans-serif; 29 background: var(--mahogany); 30 color: var(--lavender); 31 min-height: 100vh; 32 padding: 2.5rem 1.25rem; 33 } 34 35 .container { 36 max-width: 56.25rem; 37 margin: 0 auto; 38 } 39 40 h1 { 41 font-size: 2rem; 42 font-weight: 700; 43 background: linear-gradient(135deg, var(--old-rose), var(--berry-crush), var(--rosewood)); 44 -webkit-background-clip: text; 45 -webkit-text-fill-color: transparent; 46 background-clip: text; 47 letter-spacing: -0.125rem; 48 margin-bottom: 0.5rem; 49 } 50 51 .subtitle { 52 color: var(--old-rose); 53 margin-bottom: 2rem; 54 font-size: 1rem; 55 font-weight: 300; 56 } 57 58 .section { 59 background: rgba(188, 141, 160, 0.05); 60 border: 1px solid var(--old-rose); 61 padding: 2rem; 62 margin-bottom: 1.5rem; 63 } 64 65 .section h2 { 66 font-size: 1.25rem; 67 font-weight: 600; 68 color: var(--lavender); 69 margin-bottom: 1.5rem; 70 } 71 72 label { 73 display: block; 74 color: var(--old-rose); 75 font-size: 0.875rem; 76 font-weight: 500; 77 margin-bottom: 0.5rem; 78 text-transform: uppercase; 79 letter-spacing: 0.05rem; 80 } 81 82 input[type="text"], 83 input[type="url"] { 84 width: 100%; 85 padding: 0.875rem 1rem; 86 background: rgba(12, 23, 19, 0.6); 87 border: 2px solid var(--rosewood); 88 border-radius: 0; 89 color: var(--lavender); 90 font-size: 1rem; 91 font-family: "Space Grotesk", sans-serif; 92 margin-bottom: 1.5rem; 93 transition: border-color 0.2s; 94 } 95 96 input:focus { 97 outline: none; 98 border-color: var(--berry-crush); 99 background: rgba(12, 23, 19, 0.8); 100 } 101 102 .checkbox-group { 103 margin-bottom: 1.5rem; 104 } 105 106 .checkbox-group label { 107 display: flex; 108 align-items: center; 109 gap: 0.5rem; 110 text-transform: none; 111 font-weight: 400; 112 margin-bottom: 0.75rem; 113 cursor: pointer; 114 } 115 116 input[type="checkbox"] { 117 width: 1.25rem; 118 height: 1.25rem; 119 cursor: pointer; 120 } 121 122 button { 123 position: relative; 124 padding: 1rem 2rem; 125 background: var(--berry-crush); 126 color: var(--lavender); 127 border: 4px solid var(--mahogany); 128 border-radius: 0; 129 font-size: 1rem; 130 font-weight: 700; 131 cursor: pointer; 132 font-family: "Space Grotesk", sans-serif; 133 transition: all 0.15s ease; 134 text-transform: uppercase; 135 letter-spacing: 0.1rem; 136 box-shadow: 6px 6px 0 var(--mahogany); 137 width: 100%; 138 } 139 140 button::before { 141 content: ''; 142 position: absolute; 143 top: -4px; 144 left: -4px; 145 right: -4px; 146 bottom: -4px; 147 background: transparent; 148 border: 4px solid var(--rosewood); 149 pointer-events: none; 150 transition: all 0.15s ease; 151 } 152 153 button:hover:not(:disabled) { 154 transform: translate(3px, 3px); 155 box-shadow: 3px 3px 0 var(--mahogany); 156 } 157 158 button:hover:not(:disabled)::before { 159 top: -7px; 160 left: -7px; 161 right: -7px; 162 bottom: -7px; 163 } 164 165 button:active:not(:disabled) { 166 transform: translate(6px, 6px); 167 box-shadow: 0 0 0 var(--mahogany); 168 } 169 170 button:disabled { 171 opacity: 0.5; 172 cursor: not-allowed; 173 } 174 175 .result { 176 background: rgba(12, 23, 19, 0.6); 177 border: 2px solid var(--rosewood); 178 padding: 1.5rem; 179 margin-top: 1.5rem; 180 font-family: monospace; 181 font-size: 0.875rem; 182 white-space: pre-wrap; 183 word-break: break-all; 184 display: none; 185 } 186 187 .result.show { 188 display: block; 189 } 190 191 .result.success { 192 border-color: #81c784; 193 background: rgba(139, 195, 74, 0.1); 194 } 195 196 .result.error { 197 border-color: var(--rosewood); 198 background: rgba(160, 70, 104, 0.1); 199 } 200 201 .info-box { 202 background: rgba(188, 141, 160, 0.1); 203 border-left: 3px solid var(--berry-crush); 204 padding: 1rem; 205 margin-bottom: 1.5rem; 206 font-size: 0.875rem; 207 color: var(--old-rose); 208 } 209 210 .info-box strong { 211 color: var(--lavender); 212 } 213 214 code { 215 background: rgba(12, 23, 19, 0.8); 216 padding: 0.125rem 0.375rem; 217 font-family: monospace; 218 color: var(--berry-crush); 219 } 220 221 a { 222 color: var(--berry-crush); 223 text-decoration: none; 224 } 225 226 a:hover { 227 text-decoration: underline; 228 } 229 230 /* JSON syntax highlighting */ 231 .json-key { 232 color: var(--berry-crush); 233 } 234 235 .json-string { 236 color: #a5d6a7; 237 } 238 239 .json-number { 240 color: #81c784; 241 } 242 243 .json-boolean { 244 color: var(--old-rose); 245 } 246 247 .json-null { 248 color: #9e9e9e; 249 } 250 </style> 251</head> 252 253<body> 254 <div class="container"> 255 <h1>oauth test client</h1> 256 <p class="subtitle">test your indiko indieauth/oauth 2.0 server</p> 257 258 <div class="section"> 259 <h2>step 1: configure</h2> 260 261 <div class="info-box"> 262 <strong>How this works:</strong><br> 263 This page simulates an OAuth client (like your blog or app). It will redirect you to indiko for authentication, 264 show you a consent screen, then exchange the authorization code for your user profile. 265 </div> 266 267 <label for="clientId">client id (your app's URL)</label> 268 <input type="url" id="clientId" value="" placeholder="https://example.com" /> 269 270 <label for="redirectUri">redirect uri (callback URL)</label> 271 <input type="url" id="redirectUri" value="" placeholder="https://example.com/callback" /> 272 273 <div class="checkbox-group"> 274 <label>scopes to request:</label> 275 <label> 276 <input type="checkbox" name="scope" value="profile" checked /> 277 <span>profile (name, photo, URL)</span> 278 </label> 279 <label> 280 <input type="checkbox" name="scope" value="email" /> 281 <span>email</span> 282 </label> 283 </div> 284 285 <button type="button" id="startBtn">start oauth flow</button> 286 </div> 287 288 <div class="section" id="callbackSection" style="display: none;"> 289 <h2>step 2: callback handler</h2> 290 291 <div class="info-box"> 292 You've been redirected back with an authorization code. Click below to exchange it for user data. 293 </div> 294 295 <div id="callbackInfo"></div> 296 297 <button type="button" id="exchangeBtn">exchange code for profile</button> 298 </div> 299 300 <div class="section" id="resultSection" style="display: none;"> 301 <h2>step 3: result</h2> 302 <div id="result" class="result"></div> 303 </div> 304 305 <div class="section"> 306 <h2>development notes</h2> 307 <ul style="list-style: none; line-height: 1.8;"> 308 <li>• This page handles the OAuth callback at the current URL</li> 309 <li>• Set <code>redirect_uri</code> to the current page URL (it will be auto-filled)</li> 310 <li><code>client_id</code> should be a valid URL (can be any URL, it auto-registers)</li> 311 <li>• Authorization codes expire in 60 seconds</li> 312 <li>• Codes are single-use only</li> 313 <li>• PKCE (S256) is required and handled automatically</li> 314 </ul> 315 </div> 316 317 <div style="text-align: center; margin-top: 2rem;"> 318 <a href="/">← back to dashboard</a> 319 </div> 320 </div> 321 322 <script type="module" src="../client/oauth-test.ts"></script> 323</body> 324 325</html>