aqhome: more work on http server.
This commit is contained in:
388
apps/aqhome-storage/u_login.c
Normal file
388
apps/aqhome-storage/u_login.c
Normal file
@@ -0,0 +1,388 @@
|
||||
/****************************************************************************
|
||||
* This file is part of the project AqHome.
|
||||
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
|
||||
*
|
||||
* The license for this file can be found in the file COPYING which you
|
||||
* should have received along with this file.
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
|
||||
#include "./u_login.h"
|
||||
#include "aqhome/http/httpservice.h"
|
||||
#include "aqhome/http/httpservice_http.h"
|
||||
#include "aqhome/http/httpservice_conf.h"
|
||||
|
||||
#include <gwenhywfar/gwenhywfar.h>
|
||||
#include <gwenhywfar/args.h>
|
||||
#include <gwenhywfar/debug.h>
|
||||
#include <gwenhywfar/endpoint.h>
|
||||
#include <gwenhywfar/mdigest.h>
|
||||
#include <gwenhywfar/text.h>
|
||||
#include <gwenhywfar/timestamp.h>
|
||||
|
||||
|
||||
|
||||
static int _handleUrl(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq);
|
||||
static int _handleGet(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq);
|
||||
static int _handlePost(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq);
|
||||
|
||||
static int _loginUser(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq);
|
||||
static int _verifyPass(AQH_USER *u, const char *userName, const char *password);
|
||||
static AQH_SESSION *_generateSessionForUser(AQH_SERVICE *sv, AQH_USER *u);
|
||||
static GWEN_BUFFER *_generateSessionUid(void);
|
||||
static void _headerSetCookie(GWEN_DB_NODE *db, uint32_t flags, const char *cookieName, const char *cookieValue);
|
||||
static GWEN_MSG *_createLoginResponseMsg(AQH_SERVICE *sv, const char *protocol, const char *sessionId, const char *newPage);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
AQH_HTTP_URLHANDLER *AQH_LoginHttpUrlHandler_new(AQH_SERVICE *sv)
|
||||
{
|
||||
AQH_HTTP_URLHANDLER *uh;
|
||||
|
||||
uh=AQH_HttpUrlHandler_new(sv);
|
||||
AQH_HttpUrlHandler_SetHandleFn(uh, _handleUrl);
|
||||
|
||||
return uh;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int _handleUrl(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq)
|
||||
{
|
||||
const char *protocol;
|
||||
const char *cmd;
|
||||
GWEN_MSG *msgOut=NULL;
|
||||
|
||||
protocol=AQH_HttpRequest_GetProtocol(rq);
|
||||
cmd=AQH_HttpRequest_GetCommand(rq);
|
||||
if (cmd && *cmd) {
|
||||
int rv;
|
||||
|
||||
if (strcasecmp(cmd, "GET")==0) {
|
||||
rv=_handleGet(uh, rq);
|
||||
if (rv<0) {
|
||||
DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
else if (strcasecmp(cmd, "POST")==0) {
|
||||
rv=_handlePost(uh, rq);
|
||||
if (rv<0) {
|
||||
DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
else {
|
||||
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 405, "Method not allowed", protocol, NULL);
|
||||
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
DBG_ERROR(AQH_LOGDOMAIN, "No command in request");
|
||||
return GWEN_ERROR_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int _handleGet(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq)
|
||||
{
|
||||
GWEN_BUFFER *pageBuf;
|
||||
int rv;
|
||||
GWEN_MSG *msgOut=NULL;
|
||||
const char *protocol;
|
||||
|
||||
protocol=AQH_HttpRequest_GetProtocol(rq);
|
||||
pageBuf=GWEN_Buffer_new(0, 256, 0, 1);
|
||||
rv=AQH_HttpUrlHandler_AddContentHeaders(uh, AQH_HTTP_CONTENT_MODE_DESKTOP, pageBuf);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(AQH_LOGDOMAIN, "Error adding headers");
|
||||
GWEN_Buffer_free(pageBuf);
|
||||
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
|
||||
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
|
||||
return 0;
|
||||
}
|
||||
|
||||
rv=AQH_HttpService_AddFile(AQH_HttpUrlHandler_GetHttpService(uh), NULL, "login.html", pageBuf);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(AQH_LOGDOMAIN, "Error reading file \"login.html\"");
|
||||
GWEN_Buffer_free(pageBuf);
|
||||
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
|
||||
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
|
||||
return 0;
|
||||
}
|
||||
|
||||
rv=AQH_HttpUrlHandler_AddContentFooters(uh, AQH_HTTP_CONTENT_MODE_DESKTOP, pageBuf);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(AQH_LOGDOMAIN, "Error adding footers");
|
||||
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
|
||||
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
|
||||
GWEN_Buffer_free(pageBuf);
|
||||
return rv;
|
||||
}
|
||||
|
||||
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 200, "OK", protocol, GWEN_Buffer_GetStart(pageBuf));
|
||||
GWEN_Buffer_free(pageBuf);
|
||||
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int _handlePost(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq)
|
||||
{
|
||||
GWEN_DB_NODE *db;
|
||||
int rv;
|
||||
|
||||
DBG_ERROR(NULL, "Login POST:");
|
||||
db=AQH_HttpRequest_GetDbPostBody(rq);
|
||||
GWEN_DB_Dump(db, 2);
|
||||
|
||||
rv=_loginUser(uh, rq);
|
||||
if (rv<0) {
|
||||
DBG_INFO(NULL, "here (%d)", rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int _loginUser(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq)
|
||||
{
|
||||
AQH_SERVICE *sv;
|
||||
GWEN_MSG *msgOut=NULL;
|
||||
const char *protocol;
|
||||
GWEN_DB_NODE *db;
|
||||
const char *userName;
|
||||
const char *password;
|
||||
AQH_USER *u;
|
||||
AQH_SESSION *session;
|
||||
int i;
|
||||
int rv;
|
||||
|
||||
sv=AQH_HttpUrlHandler_GetHttpService(uh);
|
||||
db=AQH_HttpRequest_GetDbPostBody(rq);
|
||||
protocol=AQH_HttpRequest_GetProtocol(rq);
|
||||
|
||||
userName=GWEN_DB_GetCharValue(db, "username", 0, NULL);
|
||||
password=GWEN_DB_GetCharValue(db, "password", 0, NULL);
|
||||
if (!(userName && *userName && password && *password)) {
|
||||
msgOut=AQH_HttpService_CreateResponseMsg(sv, 401, "Unauthorized", protocol, NULL);
|
||||
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
|
||||
return 0;
|
||||
}
|
||||
|
||||
u=AQH_HttpService_GetUser(sv, userName);
|
||||
if (u==NULL) {
|
||||
DBG_INFO(NULL, "User \"%s\" not found", userName);
|
||||
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 401, "Unauthorized", protocol, NULL);
|
||||
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
|
||||
return 0;
|
||||
}
|
||||
|
||||
i=AQH_User_GetState(u);
|
||||
if (i==AQH_UserState_Suspended) {
|
||||
DBG_INFO(NULL, "User \"%s\" suspended", userName);
|
||||
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 401, "Unauthorized", protocol, NULL);
|
||||
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
|
||||
return 0;
|
||||
}
|
||||
|
||||
rv=_verifyPass(u, userName, password);
|
||||
if (rv<0) {
|
||||
if (rv==GWEN_ERROR_VERIFY)
|
||||
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 401, "Unauthorized", protocol, "error");
|
||||
else
|
||||
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
|
||||
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
|
||||
return 0;
|
||||
}
|
||||
|
||||
session=_generateSessionForUser(sv, u);
|
||||
if (session==NULL) {
|
||||
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
|
||||
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DBG_ERROR(NULL, "Session generated");
|
||||
AQH_User_SetTimestampLastLogin(u, AQH_Session_GetTimestampCreation(session));
|
||||
rv=AQH_HttpService_WriteUser(sv, u);
|
||||
if (rv<0) {
|
||||
DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
|
||||
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
|
||||
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
|
||||
return 0;
|
||||
}
|
||||
|
||||
msgOut=_createLoginResponseMsg(sv, protocol, AQH_Session_GetUid(session), "/rooms/list");
|
||||
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int _verifyPass(AQH_USER *u, const char *userName, const char *password)
|
||||
{
|
||||
GWEN_MDIGEST *md;
|
||||
int rv;
|
||||
const char *storedPasswordHash;
|
||||
char buffer[70];
|
||||
|
||||
storedPasswordHash=AQH_User_GetHashedPassword(u);
|
||||
if (!(storedPasswordHash && *storedPasswordHash)) {
|
||||
DBG_ERROR(NULL, "No password hash stored with user \"%s\"", userName);
|
||||
return GWEN_ERROR_INTERNAL;
|
||||
}
|
||||
|
||||
md=GWEN_MDigest_Sha256_new();
|
||||
rv=GWEN_MDigest_Begin(md);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(NULL, "Error digesting given password [begin] (%d)", rv);
|
||||
GWEN_MDigest_free(md);
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv=GWEN_MDigest_Update(md, (const uint8_t*) password, strlen(password));
|
||||
if (rv<0) {
|
||||
DBG_ERROR(NULL, "Error digesting given password [update] (%d)", rv);
|
||||
GWEN_MDigest_free(md);
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv=GWEN_MDigest_End(md);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(NULL, "Error digesting given password [end] (%d)", rv);
|
||||
GWEN_MDigest_free(md);
|
||||
return rv;
|
||||
}
|
||||
|
||||
DBG_ERROR(NULL, "Digest needs %d bytes", (GWEN_MDigest_GetDigestSize(md)*2)+1);
|
||||
if (NULL==GWEN_Text_ToHex((const char*) GWEN_MDigest_GetDigestPtr(md), GWEN_MDigest_GetDigestSize(md), buffer, sizeof(buffer)-1)) {
|
||||
DBG_ERROR(NULL, "Buffer too small (need %d)", (GWEN_MDigest_GetDigestSize(md)*2)+1);
|
||||
GWEN_MDigest_free(md);
|
||||
return GWEN_ERROR_INTERNAL;
|
||||
}
|
||||
GWEN_MDigest_free(md);
|
||||
|
||||
if (strcasecmp(buffer, storedPasswordHash)!=0) {
|
||||
DBG_ERROR(NULL, "Bad password for user \"%s\"", userName);
|
||||
return GWEN_ERROR_VERIFY;
|
||||
}
|
||||
|
||||
DBG_ERROR(NULL, "Valid password for user \"%s\"", userName);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
AQH_SESSION *_generateSessionForUser(AQH_SERVICE *sv, AQH_USER *u)
|
||||
{
|
||||
AQH_SESSION *session;
|
||||
GWEN_BUFFER *buf;
|
||||
GWEN_TIMESTAMP *ts;
|
||||
int rv;
|
||||
|
||||
buf=_generateSessionUid();
|
||||
if (buf==NULL) {
|
||||
DBG_INFO(NULL, "here");
|
||||
return NULL;
|
||||
}
|
||||
DBG_INFO(NULL, "New session id: [%s]", GWEN_Buffer_GetStart(buf));
|
||||
|
||||
session=AQH_Session_new();
|
||||
AQH_Session_SetUid(session, GWEN_Buffer_GetStart(buf));
|
||||
|
||||
ts=GWEN_Timestamp_NowInGmTime();
|
||||
AQH_Session_SetTimestampCreation(session, ts);
|
||||
GWEN_Timestamp_free(ts);
|
||||
|
||||
AQH_Session_SetUserAlias(session, AQH_User_GetAlias(u));
|
||||
AQH_Session_SetUser(session, u);
|
||||
|
||||
rv=AQH_HttpService_AddSession(sv, session);
|
||||
if (rv<0) {
|
||||
DBG_INFO(NULL, "Error adding session \"%s\" (%d)", AQH_Session_GetUid(session), rv);
|
||||
AQH_Session_free(session);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
|
||||
|
||||
GWEN_BUFFER *_generateSessionUid(void)
|
||||
{
|
||||
uint8_t binbuf[8];
|
||||
GWEN_BUFFER *buf;
|
||||
int rv;
|
||||
|
||||
rv=GWEN_SyncIo_Helper_PartiallyReadFile("/dev/urandom", binbuf, sizeof(binbuf));
|
||||
if (rv<sizeof(binbuf)) {
|
||||
DBG_ERROR(NULL, "Error reading from /dev/urandom: %d", rv);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buf=GWEN_Buffer_new(0, 20, 0, 1);
|
||||
rv=GWEN_Text_ToHexBuffer((const char*) binbuf, rv, buf, 0, 0, 0);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(NULL, "Error converting random bytes to hex (%d)", rv);
|
||||
GWEN_Buffer_free(buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
|
||||
GWEN_MSG *_createLoginResponseMsg(AQH_SERVICE *sv, const char *protocol, const char *sessionId, const char *newPage)
|
||||
{
|
||||
GWEN_BUFFER *buf;
|
||||
GWEN_MSG *msg;
|
||||
GWEN_DB_NODE *db;
|
||||
|
||||
buf=GWEN_Buffer_new(0, 256, 0, 1);
|
||||
AQH_HttpService_AddStatusLine(sv, 303, "OK, redirecting to content", protocol, buf);
|
||||
db=GWEN_DB_Group_new("header");
|
||||
GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Connection", "Keep-Alive");
|
||||
GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Content-Type", "text/html; charset=utf-8");
|
||||
_headerSetCookie(db, GWEN_DB_FLAGS_OVERWRITE_VARS, AQH_HTTP_REQUEST_SESSIONCOOKIE, sessionId);
|
||||
GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Location", newPage);
|
||||
GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Content-Length", 0);
|
||||
AQH_HttpService_AddHeader(sv, db, buf);
|
||||
GWEN_DB_Group_free(db);
|
||||
|
||||
msg=GWEN_Msg_fromBytes((const uint8_t*)GWEN_Buffer_GetStart(buf), GWEN_Buffer_GetUsedBytes(buf));
|
||||
GWEN_Buffer_free(buf);
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void _headerSetCookie(GWEN_DB_NODE *db, uint32_t flags, const char *cookieName, const char *cookieValue)
|
||||
{
|
||||
if (cookieName && cookieValue) {
|
||||
GWEN_BUFFER *dbuf;
|
||||
|
||||
dbuf=GWEN_Buffer_new(0, 32, 0, 1);
|
||||
GWEN_Buffer_AppendArgs(dbuf, "%s=%s", cookieName, cookieValue);
|
||||
GWEN_DB_SetCharValue(db, flags, "Set-Cookie", GWEN_Buffer_GetStart(dbuf));
|
||||
GWEN_Buffer_free(dbuf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user