annotate mrjunejune/admin_api.c @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents 04fee26ecce0
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1 #include "mrjunejune/admin_api.h"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2 #include "mrjunejune/auth_api.h"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3 #include "mrjunejune/template_renderer.h"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
4
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
5 #include "auth/auth_store.h"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
6 #include "auth/auth_crypto.h"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
7 #include "seobeo/seobeo.h"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
8 #include "dowa/dowa.h"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
9
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
10 #include <openssl/crypto.h>
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
11
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
12 #include <stdio.h>
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
13 #include <stdlib.h>
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
14 #include <string.h>
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
15 #include <time.h>
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
16
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
17 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
18 /* Constants */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
19 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
20
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
21 #define ADMIN_BODY_MAX_BYTES 4096
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
22 #define ADMIN_UUID_SIZE 37
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
23
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
24 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
25 /* Internal helpers */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
26 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
27
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
28 static const char *admin_req_value(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
29 Seobeo_Request_Entry *p_req,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
30 const char *key)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
31 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
32 void *p = Dowa_HashMap_Get_Ptr(p_req, (char *)key);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
33 return p ? ((Seobeo_Request_Entry *)p)->value : NULL;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
34 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
35
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
36 static boolean admin_extract_secret_field(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
37 Dowa_JSON_Entry *obj,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
38 const char *key,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
39 char *out_buf,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
40 size_t max_len)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
41 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
42 char *arena_ptr = Dowa_JSON_Get_String(obj, key);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
43 if (!arena_ptr || arena_ptr[0] == '\0')
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
44 return FALSE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
45
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
46 size_t field_len = strlen(arena_ptr);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
47 if (field_len > max_len)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
48 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
49 OPENSSL_cleanse(arena_ptr, field_len);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
50 return FALSE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
51 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
52
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
53 memcpy(out_buf, arena_ptr, field_len);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
54 out_buf[field_len] = '\0';
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
55 OPENSSL_cleanse(arena_ptr, field_len);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
56 return TRUE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
57 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
58
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
59 static Seobeo_Request_Entry *admin_json_response(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
60 Dowa_Arena *p_arena,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
61 const char *status,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
62 const char *body)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
63 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
64 Seobeo_Request_Entry *resp = NULL;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
65 Dowa_HashMap_Push_Arena(resp, "status", (char *)status, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
66 Dowa_HashMap_Push_Arena(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
67 resp, "content-type", "application/json; charset=utf-8", p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
68 Dowa_HashMap_Push_Arena(resp, "cache-control", "no-store", p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
69 Dowa_HashMap_Push_Arena(resp, "pragma", "no-cache", p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
70 Dowa_HashMap_Push_Arena(resp, "x-content-type-options", "nosniff", p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
71 Dowa_HashMap_Push_Arena(resp, "body", (char *)body, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
72 return resp;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
73 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
74
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
75 static Seobeo_Request_Entry *admin_error(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
76 Dowa_Arena *p_arena,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
77 const char *status,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
78 const char *code,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
79 const char *message)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
80 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
81 char buf[512];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
82 snprintf(buf, sizeof(buf),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
83 "{\"error\":{\"code\":\"%s\",\"message\":\"%s\"}}",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
84 code, message);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
85 char *body = Dowa_Arena_Allocate(p_arena, strlen(buf) + 1);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
86 if (body) strcpy(body, buf);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
87 return admin_json_response(p_arena, status, body ? body : "{}");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
88 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
89
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
90 static Seobeo_Request_Entry *admin_html_redirect(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
91 Dowa_Arena *p_arena,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
92 const char *location)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
93 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
94 Seobeo_Request_Entry *resp = NULL;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
95 Dowa_HashMap_Push_Arena(resp, "status", "302", p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
96 Dowa_HashMap_Push_Arena(resp, "Location", (char *)location, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
97 Dowa_HashMap_Push_Arena(resp, "body", "", p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
98 return resp;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
99 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
100
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
101 /*
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
102 * Gate: resolve principal, enforce admin+active, reject forced-password.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
103 * On failure sets *pp_err_resp and returns FALSE.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
104 * On success fills *p_principal and returns TRUE.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
105 *
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
106 * For page requests (is_page==TRUE), unauthenticated → 302 redirect.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
107 * For API requests (is_page==FALSE), unauthenticated → 401 JSON.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
108 */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
109 static boolean admin_require_admin(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
110 Seobeo_Request_Entry *p_req,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
111 Auth_Principal *p_principal,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
112 Dowa_Arena *p_arena,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
113 boolean is_page,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
114 Seobeo_Request_Entry **pp_err_resp)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
115 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
116 Auth_Store *store = Auth_API_Get_Store();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
117 if (!store)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
118 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
119 *pp_err_resp = admin_error(p_arena, "503", "service_unavailable",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
120 "Auth not initialised");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
121 return FALSE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
122 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
123
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
124 boolean found = FALSE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
125 if (!Auth_API_Resolve_Existing_Principal(p_req, p_principal, p_arena, &found))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
126 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
127 *pp_err_resp = admin_error(p_arena, "500", "internal_error",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
128 "Session error");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
129 return FALSE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
130 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
131
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
132 if (!found || p_principal->kind != AUTH_PRINCIPAL_USER)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
133 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
134 *pp_err_resp = is_page
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
135 ? admin_html_redirect(p_arena, "/login")
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
136 : admin_error(p_arena, "401", "unauthenticated",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
137 "Authentication required");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
138 return FALSE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
139 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
140
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
141 if (p_principal->must_change_password)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
142 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
143 *pp_err_resp = is_page
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
144 ? admin_html_redirect(p_arena, "/account/password")
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
145 : admin_error(p_arena, "403", "password_change_required",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
146 "Password change required");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
147 return FALSE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
148 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
149
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
150 if (strcmp(p_principal->role, "admin") != 0)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
151 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
152 *pp_err_resp = admin_error(p_arena, "403", "forbidden",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
153 "Admin access required");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
154 return FALSE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
155 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
156
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
157 *pp_err_resp = NULL;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
158 return TRUE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
159 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
160
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
161 /*
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
162 * Validate that `:id` param is a non-empty UUID-shaped string.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
163 * Fills id_out (capacity >= 37). Returns FALSE on invalid/missing.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
164 */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
165 static boolean admin_get_id_param(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
166 Seobeo_Request_Entry *p_req,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
167 char *id_out,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
168 size_t capacity)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
169 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
170 void *kv = Dowa_HashMap_Get_Ptr(p_req, ":id");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
171 if (!kv) return FALSE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
172 const char *val = ((Seobeo_Request_Entry *)kv)->value;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
173 if (!val || val[0] == '\0') return FALSE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
174 size_t vlen = strlen(val);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
175 if (vlen != 36) return FALSE; /* UUID is 36 chars + NUL */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
176 if (vlen >= capacity) return FALSE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
177 memcpy(id_out, val, vlen);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
178 id_out[vlen] = '\0';
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
179 return TRUE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
180 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
181
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
182 /*
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
183 * Append one JSON user object to buf (returns new offset, -1 on error).
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
184 * Never includes password_hash, session digests, or guest IDs.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
185 * p_arena is used for temporary string escaping.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
186 */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
187 static int admin_append_user_json(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
188 char *buf,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
189 size_t capacity,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
190 size_t offset,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
191 boolean is_first,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
192 const Auth_User_Record *u,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
193 Dowa_Arena *p_arena)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
194 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
195 char *safe_id = Dowa_JSON_Escape_String(u->id, 0, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
196 char *safe_user = Dowa_JSON_Escape_String(u->username, 0, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
197 char *safe_role = Dowa_JSON_Escape_String(u->role, 0, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
198 char *safe_stat = Dowa_JSON_Escape_String(u->status, 0, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
199 if (!safe_id || !safe_user || !safe_role || !safe_stat)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
200 return -1;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
201
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
202 int n = snprintf(buf + offset, capacity - offset,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
203 "%s{"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
204 "\"id\":\"%s\","
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
205 "\"username\":\"%s\","
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
206 "\"role\":\"%s\","
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
207 "\"status\":\"%s\","
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
208 "\"mustChangePassword\":%s,"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
209 "\"createdAt\":%lld,"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
210 "\"updatedAt\":%lld,"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
211 "\"passwordChangedAt\":%lld"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
212 "}",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
213 is_first ? "" : ",",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
214 safe_id, safe_user, safe_role, safe_stat,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
215 u->must_change_password ? "true" : "false",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
216 (long long)u->created_at,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
217 (long long)u->updated_at,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
218 (long long)u->password_changed_at);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
219
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
220 if (n < 0 || (size_t)n >= capacity - offset)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
221 return -1;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
222 return (int)(offset + (size_t)n);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
223 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
224
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
225 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
226 /* Route: GET /admin/users (page) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
227 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
228
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
229 static Seobeo_Request_Entry *admin_page_handler(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
230 Seobeo_Request_Entry *p_req,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
231 Dowa_Arena *p_arena)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
232 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
233 Auth_Principal principal;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
234 Seobeo_Request_Entry *err = NULL;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
235 if (!admin_require_admin(p_req, &principal, p_arena, TRUE, &err))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
236 return err;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
237
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
238 char *body = Dowa_Arena_Allocate(p_arena, 128 * 1024);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
239 if (!body || !Mjj_Template_Render_File(body, 128 * 1024, "/admin/users/index.html", p_arena))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
240 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
241 return admin_error(p_arena, "500", "internal_error", "Render failed");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
242 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
243
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
244 Seobeo_Request_Entry *resp = NULL;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
245 Dowa_HashMap_Push_Arena(resp, "status", "200", p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
246 Dowa_HashMap_Push_Arena(resp, "content-type", "text/html; charset=utf-8", p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
247 Dowa_HashMap_Push_Arena(resp, "cache-control", "no-store", p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
248 Dowa_HashMap_Push_Arena(resp, "pragma", "no-cache", p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
249 Dowa_HashMap_Push_Arena(resp, "x-content-type-options", "nosniff", p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
250 Dowa_HashMap_Push_Arena(resp, "referrer-policy", "no-referrer", p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
251 Dowa_HashMap_Push_Arena(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
252 resp, "content-security-policy", "frame-ancestors 'none'", p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
253 Dowa_HashMap_Push_Arena(resp, "body", body, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
254 return resp;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
255 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
256
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
257 static Seobeo_Request_Entry *admin_list_handler(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
258 Seobeo_Request_Entry *p_req,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
259 Dowa_Arena *p_arena)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
260 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
261 Auth_Principal principal;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
262 Seobeo_Request_Entry *err = NULL;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
263 if (!admin_require_admin(p_req, &principal, p_arena, FALSE, &err))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
264 return err;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
265
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
266 Auth_Store *store = Auth_API_Get_Store();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
267
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
268 /* Pagination params from query string; defaults applied. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
269 int64 page = 1;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
270 int64 limit = ADMIN_API_DEFAULT_LIMIT;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
271 const char *qpage = admin_req_value(p_req, "Query-page");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
272 const char *qlimit = admin_req_value(p_req, "Query-limit");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
273 if (qpage && qpage[0] != '\0') page = atol(qpage);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
274 if (qlimit && qlimit[0] != '\0') limit = atol(qlimit);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
275 if (page < 1) page = 1;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
276 if (limit < 1) limit = 1;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
277 if (limit > ADMIN_API_MAX_LIMIT) limit = ADMIN_API_MAX_LIMIT;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
278
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
279 Dowa_Arena *list_arena = Dowa_Arena_Create(128 * 1024);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
280 if (!list_arena)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
281 return admin_error(p_arena, "500", "internal_error", "OOM");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
282
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
283 Auth_User_Record *records = NULL;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
284 Auth_Store_Result res = Auth_Store_List_Users(store, &records, list_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
285 if (res != AUTH_STORE_OK)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
286 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
287 Dowa_Arena_Free(list_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
288 return admin_error(p_arena, "500", "internal_error", "List users failed");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
289 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
290
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
291 int64 total = (int64)Dowa_Array_Length(records);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
292 int64 offset_start = total;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
293 if (page - 1 <= total / limit)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
294 offset_start = (page - 1) * limit;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
295 if (offset_start > total)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
296 offset_start = total;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
297 int64 offset_end = total - offset_start < limit
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
298 ? total
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
299 : offset_start + limit;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
300
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
301 /* Build JSON response in the request arena. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
302 size_t resp_size = 64 + (size_t)(total > 0 ? total : 1) * 400;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
303 char *body = Dowa_Arena_Allocate(p_arena, resp_size);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
304 if (!body)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
305 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
306 Dowa_Arena_Free(list_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
307 return admin_error(p_arena, "500", "internal_error", "OOM");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
308 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
309
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
310 int n = snprintf(body, resp_size,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
311 "{\"total\":%lld,\"page\":%lld,\"limit\":%lld,\"users\":[",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
312 (long long)total, (long long)page, (long long)limit);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
313 if (n < 0)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
314 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
315 Dowa_Arena_Free(list_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
316 return admin_error(p_arena, "500", "internal_error", "Encode error");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
317 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
318 size_t pos = (size_t)n;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
319 boolean first = TRUE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
320
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
321 for (int64 i = offset_start; i < offset_end && pos < resp_size - 2; i++)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
322 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
323 int nw = admin_append_user_json(body, resp_size, pos, first, &records[i], p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
324 if (nw < 0)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
325 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
326 Dowa_Arena_Free(list_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
327 return admin_error(p_arena, "500", "internal_error", "Encode error");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
328 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
329 pos = (size_t)nw;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
330 first = FALSE;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
331 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
332
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
333 if (pos + 2 >= resp_size)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
334 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
335 Dowa_Arena_Free(list_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
336 return admin_error(p_arena, "500", "internal_error", "Buffer too small");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
337 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
338 body[pos++] = ']';
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
339 body[pos++] = '}';
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
340 body[pos] = '\0';
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
341
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
342 Dowa_Arena_Free(list_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
343 return admin_json_response(p_arena, "200", body);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
344 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
345
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
346 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
347 /* Route: POST /api/admin/users (create) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
348 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
349
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
350 static Seobeo_Request_Entry *admin_create_handler(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
351 Seobeo_Request_Entry *p_req,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
352 Dowa_Arena *p_arena)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
353 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
354 Auth_Principal principal;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
355 Seobeo_Request_Entry *err = NULL;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
356 if (!admin_require_admin(p_req, &principal, p_arena, FALSE, &err))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
357 return err;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
358
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
359 if (!Auth_API_Verify_CSRF(p_req, &principal))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
360 return admin_error(p_arena, "403", "csrf_invalid", "CSRF check failed");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
361
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
362 const char *body_str = admin_req_value(p_req, "Body");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
363 if (!body_str)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
364 return admin_error(p_arena, "400", "bad_request", "Invalid request body");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
365
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
366 size_t body_len = strlen(body_str);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
367 if (body_len > ADMIN_BODY_MAX_BYTES)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
368 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
369 OPENSSL_cleanse((char *)body_str, body_len);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
370 return admin_error(p_arena, "400", "bad_request", "Invalid request body");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
371 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
372 Dowa_JSON_Value jv =
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
373 Dowa_JSON_Parse(body_str, (int32)body_len, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
374 OPENSSL_cleanse((char *)body_str, body_len);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
375 if (jv.type != DOWA_JSON_OBJECT)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
376 return admin_error(p_arena, "400", "bad_request", "Expected JSON object");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
377
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
378 Dowa_JSON_Entry *obj = (Dowa_JSON_Entry *)jv.object_val;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
379 char *username_raw = Dowa_JSON_Get_String(obj, "username");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
380 char *role_raw = Dowa_JSON_Get_String(obj, "role");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
381 char password_buf[AUTH_CRYPTO_PASSWORD_MAX_BYTES + 1];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
382 memset(password_buf, 0, sizeof(password_buf));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
383 boolean have_password = admin_extract_secret_field(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
384 obj, "temporaryPassword", password_buf,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
385 AUTH_CRYPTO_PASSWORD_MAX_BYTES);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
386 char *password_raw = password_buf;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
387
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
388 if (!username_raw || username_raw[0] == '\0' ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
389 !have_password)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
390 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
391 OPENSSL_cleanse(password_buf, sizeof(password_buf));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
392 return admin_error(p_arena, "400", "bad_request",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
393 "username and temporaryPassword required");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
394 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
395
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
396 const char *role = "member";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
397 if (role_raw && role_raw[0] != '\0')
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
398 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
399 if (strcmp(role_raw, "admin") != 0 && strcmp(role_raw, "member") != 0)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
400 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
401 OPENSSL_cleanse(password_raw, strlen(password_raw));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
402 return admin_error(p_arena, "400", "invalid_role",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
403 "role must be admin or member");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
404 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
405 role = role_raw;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
406 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
407
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
408 size_t pw_len = strlen(password_raw);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
409 if (pw_len < 12 || pw_len > AUTH_CRYPTO_PASSWORD_MAX_BYTES)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
410 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
411 OPENSSL_cleanse(password_raw, pw_len);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
412 return admin_error(p_arena, "400", "password_policy",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
413 "Password must be 12 to 1024 characters");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
414 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
415
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
416 char encoded_hash[AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
417 Auth_Crypto_Result hr =
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
418 Auth_Crypto_Password_Hash(password_raw, encoded_hash, sizeof(encoded_hash));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
419 OPENSSL_cleanse(password_raw, pw_len);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
420
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
421 if (hr != AUTH_CRYPTO_OK)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
422 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
423 OPENSSL_cleanse(encoded_hash, sizeof(encoded_hash));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
424 return admin_error(p_arena, "500", "internal_error", "Hash error");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
425 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
426
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
427 char new_id[ADMIN_UUID_SIZE];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
428 Auth_Store *store = Auth_API_Get_Store();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
429 Auth_Store_Result cr = Auth_Store_Create_User_Audited(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
430 store,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
431 username_raw,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
432 encoded_hash,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
433 role,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
434 TRUE, /* must_change_password */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
435 principal.user_id,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
436 new_id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
437 OPENSSL_cleanse(encoded_hash, sizeof(encoded_hash));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
438
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
439 if (cr == AUTH_STORE_CONFLICT)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
440 return admin_error(p_arena, "409", "conflict", "Username already exists");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
441 if (cr != AUTH_STORE_OK)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
442 return admin_error(p_arena, "500", "internal_error", "Create user failed");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
443
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
444 /* Fetch the created record for response. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
445 Auth_User_Record rec;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
446 memset(&rec, 0, sizeof(rec));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
447 if (Auth_Store_Get_User(store, new_id, &rec) != AUTH_STORE_OK)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
448 return admin_error(p_arena, "500", "internal_error", "Fetch failed");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
449
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
450 char *safe_id = Dowa_JSON_Escape_String(rec.id, 0, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
451 char *safe_user = Dowa_JSON_Escape_String(rec.username, 0, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
452 char *safe_role = Dowa_JSON_Escape_String(rec.role, 0, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
453 char *safe_stat = Dowa_JSON_Escape_String(rec.status, 0, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
454 if (!safe_id || !safe_user || !safe_role || !safe_stat)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
455 return admin_error(p_arena, "500", "internal_error", "Encode error");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
456
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
457 char body_buf[512];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
458 snprintf(body_buf, sizeof(body_buf),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
459 "{\"id\":\"%s\",\"username\":\"%s\",\"role\":\"%s\","
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
460 "\"status\":\"%s\",\"mustChangePassword\":true,"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
461 "\"createdAt\":%lld}",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
462 safe_id, safe_user, safe_role, safe_stat,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
463 (long long)rec.created_at);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
464
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
465 char *body_copy = Dowa_Arena_Allocate(p_arena, strlen(body_buf) + 1);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
466 if (!body_copy)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
467 return admin_error(p_arena, "500", "internal_error", "OOM");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
468 strcpy(body_copy, body_buf);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
469
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
470 return admin_json_response(p_arena, "201", body_copy);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
471 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
472
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
473 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
474 /* Route: PATCH /api/admin/users/:id (update) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
475 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
476
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
477 static Seobeo_Request_Entry *admin_update_handler(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
478 Seobeo_Request_Entry *p_req,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
479 Dowa_Arena *p_arena)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
480 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
481 Auth_Principal principal;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
482 Seobeo_Request_Entry *err = NULL;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
483 if (!admin_require_admin(p_req, &principal, p_arena, FALSE, &err))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
484 return err;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
485
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
486 if (!Auth_API_Verify_CSRF(p_req, &principal))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
487 return admin_error(p_arena, "403", "csrf_invalid", "CSRF check failed");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
488
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
489 char target_id[ADMIN_UUID_SIZE];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
490 if (!admin_get_id_param(p_req, target_id, sizeof(target_id)))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
491 return admin_error(p_arena, "400", "bad_request", "Invalid or missing id");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
492
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
493 const char *body_str = admin_req_value(p_req, "Body");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
494 if (!body_str)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
495 return admin_error(p_arena, "400", "bad_request", "Invalid request body");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
496
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
497 size_t body_len = strlen(body_str);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
498 if (body_len > ADMIN_BODY_MAX_BYTES)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
499 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
500 OPENSSL_cleanse((char *)body_str, body_len);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
501 return admin_error(p_arena, "400", "bad_request", "Invalid request body");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
502 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
503 Dowa_JSON_Value jv =
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
504 Dowa_JSON_Parse(body_str, (int32)body_len, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
505 OPENSSL_cleanse((char *)body_str, body_len);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
506 if (jv.type != DOWA_JSON_OBJECT)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
507 return admin_error(p_arena, "400", "bad_request", "Expected JSON object");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
508
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
509 Dowa_JSON_Entry *obj = (Dowa_JSON_Entry *)jv.object_val;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
510 char *op_raw = Dowa_JSON_Get_String(obj, "op");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
511 char temporary_password[AUTH_CRYPTO_PASSWORD_MAX_BYTES + 1];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
512 memset(temporary_password, 0, sizeof(temporary_password));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
513 boolean have_temporary_password = admin_extract_secret_field(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
514 obj, "temporaryPassword", temporary_password,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
515 AUTH_CRYPTO_PASSWORD_MAX_BYTES);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
516 if (!op_raw || op_raw[0] == '\0')
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
517 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
518 OPENSSL_cleanse(temporary_password, sizeof(temporary_password));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
519 return admin_error(p_arena, "400", "bad_request", "op field required");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
520 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
521
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
522 Auth_Store *store = Auth_API_Get_Store();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
523 Auth_Store_Result res = AUTH_STORE_OK;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
524
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
525 if (strcmp(op_raw, "enable") == 0)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
526 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
527 res = Auth_Store_Enable_User(store, target_id, principal.user_id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
528 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
529 else if (strcmp(op_raw, "disable") == 0)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
530 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
531 res = Auth_Store_Disable_User_And_Revoke_Sessions(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
532 store, target_id, principal.user_id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
533 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
534 else if (strcmp(op_raw, "set_role") == 0)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
535 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
536 char *new_role = Dowa_JSON_Get_String(obj, "role");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
537 if (!new_role || new_role[0] == '\0')
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
538 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
539 OPENSSL_cleanse(temporary_password, sizeof(temporary_password));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
540 return admin_error(p_arena, "400", "bad_request",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
541 "role required for set_role op");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
542 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
543 if (strcmp(new_role, "admin") != 0 && strcmp(new_role, "member") != 0)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
544 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
545 OPENSSL_cleanse(temporary_password, sizeof(temporary_password));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
546 return admin_error(p_arena, "400", "invalid_role",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
547 "role must be admin or member");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
548 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
549 res = Auth_Store_Update_Role_And_Revoke_Sessions(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
550 store, target_id, new_role, principal.user_id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
551 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
552 else if (strcmp(op_raw, "temp_reset") == 0)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
553 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
554 if (!have_temporary_password)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
555 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
556 OPENSSL_cleanse(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
557 temporary_password, sizeof(temporary_password));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
558 return admin_error(p_arena, "400", "bad_request",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
559 "temporaryPassword required for temp_reset op");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
560 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
561 char *pw_raw = temporary_password;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
562
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
563 size_t pw_len = strlen(pw_raw);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
564 if (pw_len < 12 || pw_len > AUTH_CRYPTO_PASSWORD_MAX_BYTES)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
565 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
566 OPENSSL_cleanse(pw_raw, pw_len);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
567 return admin_error(p_arena, "400", "password_policy",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
568 "Password must be 12 to 1024 characters");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
569 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
570
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
571 char encoded_hash[AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
572 Auth_Crypto_Result hr =
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
573 Auth_Crypto_Password_Hash(pw_raw, encoded_hash, sizeof(encoded_hash));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
574 OPENSSL_cleanse(pw_raw, pw_len);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
575
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
576 if (hr != AUTH_CRYPTO_OK)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
577 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
578 OPENSSL_cleanse(encoded_hash, sizeof(encoded_hash));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
579 return admin_error(p_arena, "500", "internal_error", "Hash error");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
580 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
581
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
582 res = Auth_Store_Admin_Reset_Password(store, target_id, encoded_hash,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
583 principal.user_id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
584 OPENSSL_cleanse(encoded_hash, sizeof(encoded_hash));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
585 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
586 else
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
587 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
588 OPENSSL_cleanse(temporary_password, sizeof(temporary_password));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
589 return admin_error(p_arena, "400", "invalid_op",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
590 "op must be enable, disable, set_role, or temp_reset");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
591 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
592
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
593 OPENSSL_cleanse(temporary_password, sizeof(temporary_password));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
594
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
595 if (res == AUTH_STORE_NOT_FOUND)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
596 return admin_error(p_arena, "404", "not_found", "User not found");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
597 if (res == AUTH_STORE_LAST_ADMIN)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
598 return admin_error(p_arena, "409", "last_admin",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
599 "Cannot remove the last active admin");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
600 if (res == AUTH_STORE_INVALID_ARG)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
601 return admin_error(p_arena, "400", "bad_request", "Invalid argument");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
602 if (res != AUTH_STORE_OK)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
603 return admin_error(p_arena, "500", "internal_error", "Update failed");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
604
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
605 /* Return updated user record. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
606 Auth_User_Record rec;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
607 memset(&rec, 0, sizeof(rec));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
608 if (Auth_Store_Get_User(store, target_id, &rec) != AUTH_STORE_OK)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
609 return admin_json_response(p_arena, "200", "{\"ok\":true}");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
610
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
611 char *safe_id = Dowa_JSON_Escape_String(rec.id, 0, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
612 char *safe_user = Dowa_JSON_Escape_String(rec.username, 0, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
613 char *safe_role = Dowa_JSON_Escape_String(rec.role, 0, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
614 char *safe_stat = Dowa_JSON_Escape_String(rec.status, 0, p_arena);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
615 if (!safe_id || !safe_user || !safe_role || !safe_stat)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
616 return admin_error(p_arena, "500", "internal_error", "Encode error");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
617
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
618 char body_buf[512];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
619 snprintf(body_buf, sizeof(body_buf),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
620 "{\"id\":\"%s\",\"username\":\"%s\",\"role\":\"%s\","
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
621 "\"status\":\"%s\",\"mustChangePassword\":%s,"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
622 "\"updatedAt\":%lld}",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
623 safe_id, safe_user, safe_role, safe_stat,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
624 rec.must_change_password ? "true" : "false",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
625 (long long)rec.updated_at);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
626
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
627 char *body_copy = Dowa_Arena_Allocate(p_arena, strlen(body_buf) + 1);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
628 if (!body_copy)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
629 return admin_error(p_arena, "500", "internal_error", "OOM");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
630 strcpy(body_copy, body_buf);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
631
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
632 return admin_json_response(p_arena, "200", body_copy);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
633 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
634
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
635 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
636 /* Route: DELETE /api/admin/users/:id/sessions (revoke sessions) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
637 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
638
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
639 static Seobeo_Request_Entry *admin_revoke_sessions_handler(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
640 Seobeo_Request_Entry *p_req,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
641 Dowa_Arena *p_arena)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
642 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
643 Auth_Principal principal;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
644 Seobeo_Request_Entry *err = NULL;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
645 if (!admin_require_admin(p_req, &principal, p_arena, FALSE, &err))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
646 return err;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
647
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
648 if (!Auth_API_Verify_CSRF(p_req, &principal))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
649 return admin_error(p_arena, "403", "csrf_invalid", "CSRF check failed");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
650
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
651 char target_id[ADMIN_UUID_SIZE];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
652 if (!admin_get_id_param(p_req, target_id, sizeof(target_id)))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
653 return admin_error(p_arena, "400", "bad_request", "Invalid or missing id");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
654
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
655 Auth_Store *store = Auth_API_Get_Store();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
656
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
657 Auth_Store_Result res = Auth_Store_Revoke_All_Sessions_Audited(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
658 store, target_id, NULL, principal.user_id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
659 if (res == AUTH_STORE_NOT_FOUND)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
660 return admin_error(p_arena, "404", "not_found", "User not found");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
661 if (res != AUTH_STORE_OK)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
662 return admin_error(p_arena, "500", "internal_error", "Revoke failed");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
663
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
664 return admin_json_response(p_arena, "200", "{\"ok\":true}");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
665 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
666
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
667 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
668 /* Public API */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
669 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
670
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
671 void Admin_API_Register_Routes(void)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
672 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
673 Seobeo_Router_Register("GET", "/admin/users", admin_page_handler);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
674 Seobeo_Router_Register("GET", "/api/admin/users", admin_list_handler);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
675 Seobeo_Router_Register("POST", "/api/admin/users", admin_create_handler);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
676 Seobeo_Router_Register("PATCH", "/api/admin/users/:id", admin_update_handler);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
677 Seobeo_Router_Register("DELETE", "/api/admin/users/:id/sessions", admin_revoke_sessions_handler);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
678 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
679
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
680 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
681 /* Test hooks */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
682 /* ------------------------------------------------------------------ */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
683
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
684 #ifdef ADMIN_API_TEST_HOOKS
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
685 Seobeo_Request_Entry *Admin_API_Test_Page_Handler(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
686 Seobeo_Request_Entry *p_req, Dowa_Arena *p_arena)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
687 { return admin_page_handler(p_req, p_arena); }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
688
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
689 Seobeo_Request_Entry *Admin_API_Test_List_Handler(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
690 Seobeo_Request_Entry *p_req, Dowa_Arena *p_arena)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
691 { return admin_list_handler(p_req, p_arena); }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
692
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
693 Seobeo_Request_Entry *Admin_API_Test_Create_Handler(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
694 Seobeo_Request_Entry *p_req, Dowa_Arena *p_arena)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
695 { return admin_create_handler(p_req, p_arena); }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
696
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
697 Seobeo_Request_Entry *Admin_API_Test_Update_Handler(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
698 Seobeo_Request_Entry *p_req, Dowa_Arena *p_arena)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
699 { return admin_update_handler(p_req, p_arena); }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
700
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
701 Seobeo_Request_Entry *Admin_API_Test_Revoke_Sessions_Handler(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
702 Seobeo_Request_Entry *p_req, Dowa_Arena *p_arena)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
703 { return admin_revoke_sessions_handler(p_req, p_arena); }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
704 #endif /* ADMIN_API_TEST_HOOKS */