Line data Source code
1 : /**
2 : Copyright (c) 2024 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #include "SPWebTools.h"
24 : #include "SPWebRoot.h"
25 : #include "SPWebSession.h"
26 : #include "SPDbUser.h"
27 :
28 : namespace STAPPLER_VERSIONIZED stappler::web::tools {
29 :
30 50 : static bool reportAuthHandlerError(Request &rctx, StringView text, Status status, Value && = Value()) {
31 50 : rctx.addError("Auth", text);
32 50 : if (status != DECLINED) {
33 25 : rctx.setStatus(status);
34 : }
35 50 : return false;
36 : }
37 :
38 225 : bool AuthHandler::isRequestPermitted(Request &rctx) {
39 225 : if (_subPath != "/login" && _subPath != "/update" && _subPath != "/cancel" && _subPath != "/setup" && _subPath != "/basic" && _subPath != "/touch") {
40 0 : return reportAuthHandlerError(rctx, "Invalid handler action", HTTP_NOT_FOUND);
41 : }
42 225 : if (_subPath != "/login" && _subPath != "/setup" && _subPath != "/basic" && rctx.getUser() == nullptr) {
43 25 : return reportAuthHandlerError(rctx, "You are not logged in", HTTP_FORBIDDEN);
44 : }
45 200 : _allow = AllowMethod::Get;
46 200 : _transaction = rctx.acquireDbTransaction();
47 200 : return rctx.getInfo().method == RequestMethod::Get;
48 : }
49 :
50 200 : Status AuthHandler::onTranslateName(Request &rctx) {
51 200 : if (_subPath == "/basic") {
52 50 : if (rctx.getAuthorizedUser()) {
53 50 : auto r = rctx.getInfo().queryData.getString("redirect");
54 50 : return rctx.redirectTo(String(r));
55 50 : }
56 0 : return HTTP_UNAUTHORIZED;
57 : }
58 150 : return DataHandler::onTranslateName(rctx);
59 : }
60 :
61 150 : bool AuthHandler::processDataHandler(Request &rctx, Value &result, Value &input) {
62 150 : auto &queryData = rctx.getInfo().queryData;
63 :
64 150 : if (!_transaction) {
65 0 : return reportAuthHandlerError(rctx, "Database connection failed", HTTP_INTERNAL_SERVER_ERROR);
66 : }
67 :
68 150 : if (_subPath == "/login") {
69 50 : auto &name = queryData.getString("name");
70 50 : auto &passwd = queryData.getString("passwd");
71 50 : if (name.empty() || passwd.empty()) {
72 0 : return reportAuthHandlerError(rctx, "Name or password is not specified", DECLINED, Value{ pair("Doc", Value("You should specify 'name' and 'passwd' variables in request")) });
73 : }
74 :
75 50 : TimeInterval maxAge = TimeInterval::seconds(queryData.getInteger("maxAge"));
76 50 : if (!maxAge || maxAge > config::AUTH_MAX_TIME) {
77 50 : maxAge = config::AUTH_MAX_TIME;
78 : }
79 :
80 50 : auto user = db::User::get(_transaction, name, passwd);
81 50 : if (!user) {
82 25 : return reportAuthHandlerError(rctx, "Invalid username or password", DECLINED);
83 : }
84 :
85 25 : auto &opts = getOptions();
86 25 : bool isAuthorized = false;
87 25 : if (!opts.getBool("AdminOnly") || user->isAdmin()) {
88 25 : isAuthorized = true;
89 : }
90 :
91 25 : if (isAuthorized) {
92 25 : Session *session = _request.authorizeUser(user, maxAge);
93 25 : if (session) {
94 25 : auto &token = session->getSessionToken();
95 25 : result.setString(base64url::encode<Interface>(CoderSource(token.data(), token.size())), "token");
96 25 : result.setInteger(maxAge.toSeconds(), "maxAge");
97 25 : result.setInteger(user->getObjectId(), "userId");
98 25 : result.setString(user->getName(), "userName");
99 25 : if (queryData.getBool("userdata")) {
100 25 : auto &val = result.emplace("userData");
101 125 : for (auto &it : *user) {
102 100 : val.setValue(it.second, it.first);
103 : }
104 25 : val.erase("password");
105 : }
106 25 : return true;
107 : }
108 : }
109 :
110 0 : return reportAuthHandlerError(rctx, "Fail to create session", DECLINED);
111 100 : } else if (_subPath == "/touch") {
112 25 : if (auto u = rctx.getAuthorizedUser()) {
113 25 : result.setInteger(u->getObjectId(), "userId");
114 25 : result.setString(u->getName(), "userName");
115 25 : return true;
116 : }
117 0 : return false;
118 75 : } else if (_subPath == "/update") {
119 25 : if (auto session = rctx.getSession()) {
120 25 : db::User *user = session->getUser();
121 25 : if (!user) {
122 25 : return false;
123 : }
124 25 : TimeInterval maxAge = TimeInterval::seconds(rctx.getInfo().queryData.getInteger("maxAge"));
125 25 : if (!maxAge || maxAge > config::AUTH_MAX_TIME) {
126 25 : maxAge = config::AUTH_MAX_TIME;
127 : }
128 :
129 25 : if (session->touch(maxAge)) {
130 25 : auto &token = session->getSessionToken();
131 25 : result.setString(base64url::encode<Interface>(CoderSource(token.data(), token.size())), "token");
132 25 : result.setInteger(maxAge.toSeconds(), "maxAge");
133 25 : result.setInteger(user->getObjectId(), "userId");
134 25 : result.setString(user->getName(), "userName");
135 25 : if (queryData.getBool("userdata")) {
136 25 : auto &val = result.emplace("userData");
137 125 : for (auto &it : *user) {
138 100 : val.setValue(it.second, it.first);
139 : }
140 25 : val.erase("password");
141 : }
142 25 : return true;
143 : }
144 : }
145 :
146 0 : return false;
147 50 : } else if (_subPath == "/cancel") {
148 25 : if (auto session = rctx.getSession()) {
149 25 : return session->cancel();
150 : }
151 0 : return false;
152 25 : } else if (_subPath == "/setup") {
153 25 : auto &name = queryData.getString("name");
154 25 : auto &passwd = queryData.getString("passwd");
155 :
156 25 : if (name.empty() || passwd.empty()) {
157 0 : return reportAuthHandlerError(rctx, "Fail to create session", DECLINED, Value{ pair("Doc", Value("You should specify 'name' and 'passwd' variables in request")) });
158 : }
159 :
160 25 : auto user = db::User::setup(_transaction, name, passwd);
161 25 : if (user) {
162 25 : Value &u = result.emplace("user");
163 125 : for (auto &it : *user) {
164 100 : u.setValue(it.second, it.first);
165 : }
166 25 : return true;
167 : } else {
168 0 : return reportAuthHandlerError(rctx, "Setup failed", DECLINED);
169 : }
170 : }
171 0 : return false;
172 : }
173 :
174 : }
|