diff --git a/TODO.txt b/TODO.txt index 6075e19..0bdce5c 100644 --- a/TODO.txt +++ b/TODO.txt @@ -91,8 +91,8 @@ Milestone: v0.3.0 - Ensure that registration tokens can be used even if registration is disabled. [~] User-Interactive fallback - [ ] Password - [ ] Registration token + [~] Password + [~] Registration token [~] 4: Account management [~] Deactivate [x] Make sure UserLogin() fails if user is deactivated. diff --git a/src/Html.c b/src/Html.c index 25ea8ce..5957891 100644 --- a/src/Html.c +++ b/src/Html.c @@ -44,64 +44,13 @@ HtmlBegin(Stream * stream, char *title) "" "" "%s | Telodendria" + "" + "" + "" + "" + "
"
                  ,title
             );
-    HtmlBeginStyle(stream);
-    StreamPuts(stream,
-               ":root {"
-               "  color-scheme: dark;"
-               "  --accent: #7b8333;"
-               "}"
-               "body {"
-               "  margin: auto;"
-               "  width: 100%;"
-               "  max-width: 8.5in;"
-               "  padding: 0.25in;"
-               "  background-color: #0d1117;"
-               "  color: white;"
-               "}"
-               "a {"
-               "  color: var(--accent);"
-               "  text-decoration: none;"
-               "}"
-               "h1 {"
-               "  text-align: center;"
-               "}"
-               ".logo {"
-               "  color: var(--accent);"
-               "  text-align: center;"
-               "  font-weight: bold;"
-               "}"
-            );
-
-    StreamPuts(stream,
-               ".form {"
-               "  margin: auto;"
-               "  width: 100%;"
-               "  max-width: 400px;"
-               "  border-radius: 10px;"
-               "  border: 1px var(--accent) solid;"
-               "  padding: 10px;"
-               "}"
-               "form {"
-               "  display: block;"
-               "}"
-               "form > input, label {"
-               "  width: 95%;"
-               "  height: 25px;"
-               "  display: block;"
-               "  margin-bottom: 5px;"
-               "  margin-left: auto;"
-               "  margin-right: auto;"
-               "}"
-            );
-    HtmlEndStyle(stream);
-
-    StreamPuts(stream,
-               ""
-               ""
-               "
"
-            );
 
     for (i = 0; i < TELODENDRIA_LOGO_HEIGHT; i++)
     {
diff --git a/src/Routes.c b/src/Routes.c
index 3cf53e2..1f63d97 100644
--- a/src/Routes.c
+++ b/src/Routes.c
@@ -46,6 +46,7 @@ RouterBuild(void)
     R("/_matrix/client/versions", RouteVersions);
 
     R("/_matrix/static", RouteStaticDefault);
+    R("/_matrix/static/telodendria\\.(js|css)", RouteStaticResources);
     R("/_matrix/static/client/login", RouteStaticLogin);
     R("/_matrix/client/v3/auth/(.*)/fallback/web", RouteUiaFallback);
 
diff --git a/src/Routes/RouteStaticLogin.c b/src/Routes/RouteStaticLogin.c
index b417b11..465fdf2 100644
--- a/src/Routes/RouteStaticLogin.c
+++ b/src/Routes/RouteStaticLogin.c
@@ -36,56 +36,18 @@ ROUTE_IMPL(RouteStaticLogin, path, argp)
 
     HtmlBegin(stream, "Log In");
 
+    HtmlBeginForm(stream, "login-form");
     StreamPuts(stream,
-               "
" - "
" "" "" "" "" "
" "" - "
"); - - HtmlBeginStyle(stream); - StreamPuts(stream, - "#error-msg {" - " display: none;" - " color: red;" - " text-align: center;" - " font-weight: bold;" - " font-size: larger;" - "}"); - HtmlEndStyle(stream); - - StreamPuts(stream, - "

" - "
" ); + HtmlEndForm(stream); HtmlBeginJs(stream); - StreamPuts(stream, - "function findGetParameter(parameterName) {" - " var result = null;" - " var tmp = [];" - " var items = location.search.substr(1).split(\"&\");" - " for (var index = 0; index < items.length; index++) {" - " tmp = items[index].split(\"=\");" - " if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);" - " }" - " return result;" - "}" - "function setError(msg) {" - " var err = document.getElementById('error-msg');" - " if (msg) {" - " err.style.display = 'block';" - " err.innerHTML = msg;" - " } else {" - " err.style.display = 'none';" - " }" - "}" - ); - StreamPuts(stream, "function buildRequest(user, pass) {" " var d = findGetParameter('device_id');" @@ -113,39 +75,26 @@ ROUTE_IMPL(RouteStaticLogin, path, argp) " if (window.onLogin) {" " window.onLogin(r);" " } else {" - " setError('Client malfunction. Your client should have defined window.onLogin()');" + " setFormError('Client error.');" " }" " } else {" - " setError(r.errcode + ': ' + r.error);" + " setFormError(r.errcode + ': ' + r.error);" " }" " }" "}" ); StreamPuts(stream, - "function sendRequest(request) {" - " var xhr = new XMLHttpRequest();" - " xhr.open('POST', '/_matrix/client/v3/login');" - " xhr.setRequestHeader('Content-Type', 'application/json');" - " xhr.onreadystatechange = () => processResponse(xhr);" - " xhr.send(JSON.stringify(request));" - "}" - ); - - StreamPuts(stream, - "window.addEventListener('load', () => {" - " document.getElementById('login-form').addEventListener('submit', (e) => {" - " e.preventDefault();" - " var user = document.getElementById('user').value;" - " var pass = document.getElementById('password').value;" - " if (!user || !pass) {" - " setError('Please provide a username and password.');" - " return;" - " }" - " setError(null);" - " var request = buildRequest(user, pass);" - " sendRequest(request);" - " });" + "onFormSubmit('login-form', (frm) => {" + " var user = document.getElementById('user').value;" + " var pass = document.getElementById('password').value;" + " if (!user || !pass) {" + " setFormError('Please provide a username and password.');" + " return;" + " }" + " setFormError(null);" + " var request = buildRequest(user, pass);" + " jsonRequest('POST', '/_matrix/client/v3/login', request, processResponse);" "});" ); HtmlEndJs(stream); diff --git a/src/Routes/RouteStaticResources.c b/src/Routes/RouteStaticResources.c new file mode 100644 index 0000000..b04450d --- /dev/null +++ b/src/Routes/RouteStaticResources.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include + +ROUTE_IMPL(RouteStaticResources, path, argp) +{ + RouteArgs *args = argp; + Stream *stream = HttpServerStream(args->context); + char *res = ArrayGet(path, 0); + + if (!res) + { + /* Should be impossible */ + HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); + return MatrixErrorCreate(M_UNKNOWN); + } + + if (strcmp(res, "js") == 0) + { + HttpResponseHeader(args->context, "Content-Type", "text/javascript"); + HttpSendHeaders(args->context); + + StreamPuts(stream, + "function findGetParameter(parameterName) {" + " var result = null;" + " var tmp = [];" + " var items = location.search.substr(1).split(\"&\");" + " for (var index = 0; index < items.length; index++) {" + " tmp = items[index].split(\"=\");" + " if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);" + " }" + " return result;" + "}" + "function setFormError(msg) {" + " var err = document.getElementById('error-msg');" + " if (msg) {" + " err.style.display = 'block';" + " err.innerHTML = msg;" + " } else {" + " err.style.display = 'none';" + " }" + "}" + ); + StreamPuts(stream, + "function jsonRequest(meth, url, json, cb) {" + " var xhr = new XMLHttpRequest();" + " xhr.open(meth, url);" + " xhr.setRequestHeader('Content-Type', 'application/json');" + " xhr.onreadystatechange = () => cb(xhr);" + " xhr.send(JSON.stringify(json));" + "}" + "function onFormSubmit(frm, cb) {" + " window.addEventListener('load', () => {" + " frm = document.getElementById(frm);" + " frm.addEventListener('submit', (e) => {" + " e.preventDefault();" + " cb(frm);" + " });" + " });" + "}" + ); + + } + else if (strcmp(res, "css") == 0) + { + HttpResponseHeader(args->context, "Content-Type", "text/css"); + HttpSendHeaders(args->context); + StreamPuts(stream, + ":root {" + " color-scheme: dark;" + " --accent: #7b8333;" + "}" + "body {" + " margin: auto;" + " width: 100%;" + " max-width: 8.5in;" + " padding: 0.25in;" + " background-color: #0d1117;" + " color: white;" + "}" + "a {" + " color: var(--accent);" + " text-decoration: none;" + "}" + "h1 {" + " text-align: center;" + "}" + ".logo {" + " color: var(--accent);" + " text-align: center;" + " font-weight: bold;" + "}"); + StreamPuts(stream, + ".form {" + " margin: auto;" + " width: 100%;" + " max-width: 400px;" + " border-radius: 10px;" + " border: 1px var(--accent) solid;" + " padding: 10px;" + "}" + "form {" + " display: block;" + "}" + "form > input, label {" + " width: 95%;" + " height: 25px;" + " display: block;" + " margin-bottom: 5px;" + " margin-left: auto;" + " margin-right: auto;" + "}" + ".form > #error-msg {" + " display: none;" + " color: red;" + " text-align: center;" + " font-weight: bold;" + " font-size: larger;" + "}"); + } + else + { + HttpResponseStatus(args->context, HTTP_NOT_FOUND); + return MatrixErrorCreate(M_NOT_FOUND); + } + + + return NULL; +} diff --git a/src/Routes/RouteUiaFallback.c b/src/Routes/RouteUiaFallback.c index dfd7114..d3d150b 100644 --- a/src/Routes/RouteUiaFallback.c +++ b/src/Routes/RouteUiaFallback.c @@ -50,17 +50,43 @@ ROUTE_IMPL(RouteUiaFallback, path, argp) HttpSendHeaders(args->context); HtmlBegin(stream, "Authentication"); - if (strcmp(authType, "m.login.dummy") == 0) - { - /* TODO */ - } - else if (strcmp(authType, "m.login.password") == 0) + if (strcmp(authType, "m.login.password") == 0) { + HtmlBeginForm(stream, "auth-form"); + StreamPuts(stream, + "" + "" + "" + "" + "
" + ""); + HtmlEndForm(stream); + HtmlBeginJs(stream); /* TODO */ + StreamPuts(stream, + "function buildRequest() {" + " setFormError('Not implemented yet.');" + " return false;" + "}"); + HtmlEndJs(stream); } else if (strcmp(authType, "m.login.registration_token") == 0) { + HtmlBeginForm(stream, "auth-form"); + StreamPuts(stream, + "" + "" + "
" + ""); + HtmlEndForm(stream); + HtmlBeginJs(stream); /* TODO */ + StreamPuts(stream, + "function buildRequest() {" + " setFormError('Not implemented yet.');" + " return false;" + "}"); + HtmlEndJs(stream); } /* * TODO: implement m.login.recaptcha, m.login.sso, @@ -71,9 +97,36 @@ ROUTE_IMPL(RouteUiaFallback, path, argp) HttpResponseStatus(args->context, HTTP_NOT_FOUND); StreamPrintf(stream, "

Unknown auth type: %s

", authType); + goto finish; } - HtmlEnd(stream); + HtmlBeginJs(stream); + StreamPuts(stream, + "function processResponse(xhr) {" + " if (xhr.status == 200) {" + " if (window.onAuthDone) {" + " window.onAuthDone();" + " } else if (window.opener && window.opener.postMessage) {" + " window.opener.postMessage('authDone', '*');" + " } else {" + " setFormError('Client error.');" + " }" + " } else {" + " let r = JSON.parse(xhr.responseText);" + " setFormError(`${r.errcode}: ${r.error}`);" + " }" + "}"); + StreamPuts(stream, + "onFormSubmit('auth-form', (frm) => {" + " let request = buildRequest();" + " if (request) {" + " jsonRequest('POST', window.location.pathname, request, processResponse);" + " }" + "});"); + HtmlEndJs(stream); + +finish: + HtmlEnd(stream); return NULL; } diff --git a/src/include/Html.h b/src/include/Html.h index 8919295..172dc71 100644 --- a/src/include/Html.h +++ b/src/include/Html.h @@ -37,6 +37,15 @@ #define HtmlBeginStyle(stream) StreamPuts(stream, "") +#define HtmlBeginForm(stream, id) StreamPrintf(stream, \ + "
" \ + "
", id); + +#define HtmlEndForm(stream) StreamPuts(stream, \ + "
" \ + "

" \ + "
"); + extern void HtmlBegin(Stream *, char *); diff --git a/src/include/Routes.h b/src/include/Routes.h index 385026c..d177b61 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -68,6 +68,7 @@ ROUTE(RouteRequestToken); ROUTE(RouteUiaFallback); ROUTE(RouteStaticDefault); ROUTE(RouteStaticLogin); +ROUTE(RouteStaticResources); ROUTE(RouteProcControl); ROUTE(RouteConfig);