@sudobility/building_blocks_rn 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/building-blocks-rn-macos.podspec +20 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/src/api/ApiContext.d.ts +10 -0
- package/dist/src/api/ApiContext.js +10 -0
- package/dist/src/app/SudobilityAppRN.d.ts +13 -7
- package/dist/src/app/SudobilityAppRN.js +11 -8
- package/dist/src/components/error/ErrorBoundary.d.ts +40 -0
- package/dist/src/components/error/ErrorBoundary.js +85 -0
- package/dist/src/components/error/index.d.ts +2 -0
- package/dist/src/components/error/index.js +1 -0
- package/dist/src/components/footer/AppFooter.js +10 -1
- package/dist/src/components/header/AppHeader.d.ts +8 -0
- package/dist/src/components/header/AppHeader.js +3 -3
- package/dist/src/components/layout/AppScreenLayout.d.ts +7 -0
- package/dist/src/components/pages/AppSubscriptionPage.js +19 -5
- package/dist/src/components/pages/AppTextScreen.d.ts +8 -0
- package/dist/src/components/pages/AppTextScreen.js +2 -2
- package/dist/src/components/pages/LoginScreen.js +18 -3
- package/dist/src/components/settings/AppearanceSettings.js +18 -14
- package/dist/src/components/settings/LanguagePicker.js +20 -5
- package/dist/src/components/settings/SettingsListScreen.js +4 -4
- package/dist/src/components/subscription/SafeSubscriptionContext.js +9 -0
- package/dist/src/components/subscription/SubscriptionScreen.js +17 -4
- package/dist/src/components/toast/ToastProvider.d.ts +23 -0
- package/dist/src/components/toast/ToastProvider.js +46 -6
- package/dist/src/constants/languages.d.ts +8 -0
- package/dist/src/constants/languages.js +8 -0
- package/dist/src/i18n/index.d.ts +6 -3
- package/dist/src/i18n/index.js +6 -3
- package/dist/src/native/WebAuth.d.ts +3 -0
- package/dist/src/native/WebAuth.js +20 -0
- package/dist/src/theme/ThemeContext.d.ts +14 -0
- package/dist/src/theme/ThemeContext.js +35 -7
- package/dist/src/theme/colors.d.ts +6 -2
- package/dist/src/theme/colors.js +6 -2
- package/dist/src/theme/spacing.d.ts +5 -2
- package/dist/src/theme/spacing.js +5 -2
- package/dist/src/theme/typography.d.ts +6 -2
- package/dist/src/theme/typography.js +6 -2
- package/macos/WebAuthModule.h +4 -0
- package/macos/WebAuthModule.m +104 -0
- package/package.json +19 -11
- package/react-native.config.js +11 -0
- package/windows/WebAuthModule.cpp +245 -0
- package/windows/WebAuthModule.h +29 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#include "pch.h"
|
|
2
|
+
#include "WebAuthModule.h"
|
|
3
|
+
|
|
4
|
+
#include <bcrypt.h>
|
|
5
|
+
#include <wincrypt.h>
|
|
6
|
+
#include <shellapi.h>
|
|
7
|
+
#include <winsock2.h>
|
|
8
|
+
#include <ws2tcpip.h>
|
|
9
|
+
|
|
10
|
+
#include <string>
|
|
11
|
+
#include <vector>
|
|
12
|
+
#include <thread>
|
|
13
|
+
#include <regex>
|
|
14
|
+
|
|
15
|
+
#pragma comment(lib, "bcrypt.lib")
|
|
16
|
+
#pragma comment(lib, "crypt32.lib")
|
|
17
|
+
#pragma comment(lib, "ws2_32.lib")
|
|
18
|
+
|
|
19
|
+
namespace BuildingBlocksRN {
|
|
20
|
+
|
|
21
|
+
void WebAuthModule::Initialize(
|
|
22
|
+
winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept {
|
|
23
|
+
m_reactContext = reactContext;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
std::string WebAuthModule::Base64UrlEncode(const std::vector<uint8_t> &data) {
|
|
27
|
+
DWORD base64Len = 0;
|
|
28
|
+
CryptBinaryToStringA(data.data(), static_cast<DWORD>(data.size()),
|
|
29
|
+
CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr,
|
|
30
|
+
&base64Len);
|
|
31
|
+
|
|
32
|
+
std::string base64(base64Len, '\0');
|
|
33
|
+
CryptBinaryToStringA(data.data(), static_cast<DWORD>(data.size()),
|
|
34
|
+
CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF,
|
|
35
|
+
base64.data(), &base64Len);
|
|
36
|
+
base64.resize(base64Len);
|
|
37
|
+
|
|
38
|
+
// Convert base64 to base64url
|
|
39
|
+
std::string result;
|
|
40
|
+
result.reserve(base64.size());
|
|
41
|
+
for (char c : base64) {
|
|
42
|
+
if (c == '+')
|
|
43
|
+
result += '-';
|
|
44
|
+
else if (c == '/')
|
|
45
|
+
result += '_';
|
|
46
|
+
else if (c == '=')
|
|
47
|
+
continue; // strip padding
|
|
48
|
+
else
|
|
49
|
+
result += c;
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
void WebAuthModule::generateCodeVerifier(
|
|
55
|
+
React::ReactPromise<std::string> result) noexcept {
|
|
56
|
+
std::vector<uint8_t> randomBytes(32);
|
|
57
|
+
NTSTATUS status =
|
|
58
|
+
BCryptGenRandom(nullptr, randomBytes.data(),
|
|
59
|
+
static_cast<ULONG>(randomBytes.size()),
|
|
60
|
+
BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
|
61
|
+
if (!BCRYPT_SUCCESS(status)) {
|
|
62
|
+
result.Reject(React::ReactError{
|
|
63
|
+
"RANDOM_ERROR", "Failed to generate random bytes"});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
result.Resolve(Base64UrlEncode(randomBytes));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
void WebAuthModule::sha256(std::string input,
|
|
71
|
+
React::ReactPromise<std::string> result) noexcept {
|
|
72
|
+
BCRYPT_ALG_HANDLE hAlg = nullptr;
|
|
73
|
+
BCRYPT_HASH_HANDLE hHash = nullptr;
|
|
74
|
+
NTSTATUS status;
|
|
75
|
+
|
|
76
|
+
status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, nullptr,
|
|
77
|
+
0);
|
|
78
|
+
if (!BCRYPT_SUCCESS(status)) {
|
|
79
|
+
result.Reject(
|
|
80
|
+
React::ReactError{"HASH_ERROR", "Failed to open algorithm provider"});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
status = BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0);
|
|
85
|
+
if (!BCRYPT_SUCCESS(status)) {
|
|
86
|
+
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
87
|
+
result.Reject(
|
|
88
|
+
React::ReactError{"HASH_ERROR", "Failed to create hash"});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
status = BCryptHashData(hHash,
|
|
93
|
+
reinterpret_cast<PUCHAR>(
|
|
94
|
+
const_cast<char *>(input.data())),
|
|
95
|
+
static_cast<ULONG>(input.size()), 0);
|
|
96
|
+
if (!BCRYPT_SUCCESS(status)) {
|
|
97
|
+
BCryptDestroyHash(hHash);
|
|
98
|
+
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
99
|
+
result.Reject(
|
|
100
|
+
React::ReactError{"HASH_ERROR", "Failed to hash data"});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
std::vector<uint8_t> hashValue(32); // SHA-256 = 32 bytes
|
|
105
|
+
status = BCryptFinishHash(hHash, hashValue.data(),
|
|
106
|
+
static_cast<ULONG>(hashValue.size()), 0);
|
|
107
|
+
BCryptDestroyHash(hHash);
|
|
108
|
+
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
109
|
+
|
|
110
|
+
if (!BCRYPT_SUCCESS(status)) {
|
|
111
|
+
result.Reject(
|
|
112
|
+
React::ReactError{"HASH_ERROR", "Failed to finish hash"});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
result.Resolve(Base64UrlEncode(hashValue));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
void WebAuthModule::authenticate(
|
|
120
|
+
std::string url, std::string callbackScheme,
|
|
121
|
+
React::ReactPromise<React::JSValue> result) noexcept {
|
|
122
|
+
// Launch authentication on a background thread to avoid blocking UI
|
|
123
|
+
std::thread([url = std::move(url), callbackScheme = std::move(callbackScheme),
|
|
124
|
+
result = std::move(result)]() mutable {
|
|
125
|
+
// Initialize Winsock for this thread
|
|
126
|
+
WSADATA wsaData;
|
|
127
|
+
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
|
128
|
+
result.Reject(
|
|
129
|
+
React::ReactError{"SOCKET_ERROR", "Failed to initialize Winsock"});
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Create a TCP listener on a random port
|
|
134
|
+
SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
135
|
+
if (listenSock == INVALID_SOCKET) {
|
|
136
|
+
WSACleanup();
|
|
137
|
+
result.Reject(
|
|
138
|
+
React::ReactError{"SOCKET_ERROR", "Failed to create socket"});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
sockaddr_in addr{};
|
|
143
|
+
addr.sin_family = AF_INET;
|
|
144
|
+
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
145
|
+
addr.sin_port = 0; // OS picks a free port
|
|
146
|
+
|
|
147
|
+
if (bind(listenSock, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) ==
|
|
148
|
+
SOCKET_ERROR) {
|
|
149
|
+
closesocket(listenSock);
|
|
150
|
+
WSACleanup();
|
|
151
|
+
result.Reject(
|
|
152
|
+
React::ReactError{"SOCKET_ERROR", "Failed to bind socket"});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Get the assigned port
|
|
157
|
+
int addrLen = sizeof(addr);
|
|
158
|
+
getsockname(listenSock, reinterpret_cast<sockaddr *>(&addr), &addrLen);
|
|
159
|
+
int port = ntohs(addr.sin_port);
|
|
160
|
+
|
|
161
|
+
if (listen(listenSock, 1) == SOCKET_ERROR) {
|
|
162
|
+
closesocket(listenSock);
|
|
163
|
+
WSACleanup();
|
|
164
|
+
result.Reject(
|
|
165
|
+
React::ReactError{"SOCKET_ERROR", "Failed to listen on socket"});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Build the redirect URI and full auth URL
|
|
170
|
+
std::string redirectUri =
|
|
171
|
+
"http://127.0.0.1:" + std::to_string(port) + "/callback";
|
|
172
|
+
|
|
173
|
+
// Append redirect_uri to the auth URL
|
|
174
|
+
std::string fullUrl = url;
|
|
175
|
+
if (fullUrl.find('?') != std::string::npos)
|
|
176
|
+
fullUrl += "&redirect_uri=" + redirectUri;
|
|
177
|
+
else
|
|
178
|
+
fullUrl += "?redirect_uri=" + redirectUri;
|
|
179
|
+
|
|
180
|
+
// Open browser
|
|
181
|
+
std::wstring wUrl(fullUrl.begin(), fullUrl.end());
|
|
182
|
+
ShellExecuteW(nullptr, L"open", wUrl.c_str(), nullptr, nullptr,
|
|
183
|
+
SW_SHOWNORMAL);
|
|
184
|
+
|
|
185
|
+
// Set a timeout on the listen socket (60 seconds)
|
|
186
|
+
DWORD timeout = 60000;
|
|
187
|
+
setsockopt(listenSock, SOL_SOCKET, SO_RCVTIMEO,
|
|
188
|
+
reinterpret_cast<const char *>(&timeout), sizeof(timeout));
|
|
189
|
+
|
|
190
|
+
// Accept the redirect connection
|
|
191
|
+
SOCKET clientSock = accept(listenSock, nullptr, nullptr);
|
|
192
|
+
if (clientSock == INVALID_SOCKET) {
|
|
193
|
+
closesocket(listenSock);
|
|
194
|
+
WSACleanup();
|
|
195
|
+
result.Resolve(React::JSValue{nullptr}); // timeout / cancelled
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Read the HTTP request
|
|
200
|
+
char buf[4096];
|
|
201
|
+
int bytesRead = recv(clientSock, buf, sizeof(buf) - 1, 0);
|
|
202
|
+
if (bytesRead <= 0) {
|
|
203
|
+
closesocket(clientSock);
|
|
204
|
+
closesocket(listenSock);
|
|
205
|
+
WSACleanup();
|
|
206
|
+
result.Resolve(React::JSValue{nullptr});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
buf[bytesRead] = '\0';
|
|
210
|
+
|
|
211
|
+
// Send a simple HTML response to close the browser tab
|
|
212
|
+
const char *response =
|
|
213
|
+
"HTTP/1.1 200 OK\r\n"
|
|
214
|
+
"Content-Type: text/html\r\n"
|
|
215
|
+
"Connection: close\r\n\r\n"
|
|
216
|
+
"<html><body><p>Authentication complete. You may close this "
|
|
217
|
+
"tab.</p><script>window.close()</script></body></html>";
|
|
218
|
+
send(clientSock, response, static_cast<int>(strlen(response)), 0);
|
|
219
|
+
closesocket(clientSock);
|
|
220
|
+
closesocket(listenSock);
|
|
221
|
+
WSACleanup();
|
|
222
|
+
|
|
223
|
+
// Parse the request line to extract the callback URL
|
|
224
|
+
std::string request(buf);
|
|
225
|
+
// Extract GET /callback?... HTTP/1.1
|
|
226
|
+
std::regex requestLineRegex(R"(GET\s+(/\S+)\s+HTTP)");
|
|
227
|
+
std::smatch match;
|
|
228
|
+
if (std::regex_search(request, match, requestLineRegex)) {
|
|
229
|
+
std::string path = match[1].str();
|
|
230
|
+
auto qPos = path.find('?');
|
|
231
|
+
if (qPos == std::string::npos) {
|
|
232
|
+
result.Resolve(React::JSValue{nullptr});
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// Reconstruct the full callback URL
|
|
236
|
+
std::string callbackUrl =
|
|
237
|
+
callbackScheme + "://callback" + path.substr(qPos);
|
|
238
|
+
result.Resolve(React::JSValue{callbackUrl});
|
|
239
|
+
} else {
|
|
240
|
+
result.Resolve(React::JSValue{nullptr});
|
|
241
|
+
}
|
|
242
|
+
}).detach();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
} // namespace BuildingBlocksRN
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "pch.h"
|
|
4
|
+
#include "NativeModules.h"
|
|
5
|
+
#include <winrt/Microsoft.ReactNative.h>
|
|
6
|
+
|
|
7
|
+
namespace BuildingBlocksRN {
|
|
8
|
+
|
|
9
|
+
REACT_MODULE(WebAuthModule)
|
|
10
|
+
struct WebAuthModule {
|
|
11
|
+
REACT_INIT(Initialize)
|
|
12
|
+
void Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept;
|
|
13
|
+
|
|
14
|
+
REACT_METHOD(generateCodeVerifier)
|
|
15
|
+
void generateCodeVerifier(React::ReactPromise<std::string> result) noexcept;
|
|
16
|
+
|
|
17
|
+
REACT_METHOD(sha256)
|
|
18
|
+
void sha256(std::string input, React::ReactPromise<std::string> result) noexcept;
|
|
19
|
+
|
|
20
|
+
REACT_METHOD(authenticate)
|
|
21
|
+
void authenticate(std::string url, std::string callbackScheme,
|
|
22
|
+
React::ReactPromise<React::JSValue> result) noexcept;
|
|
23
|
+
|
|
24
|
+
private:
|
|
25
|
+
static std::string Base64UrlEncode(const std::vector<uint8_t> &data);
|
|
26
|
+
winrt::Microsoft::ReactNative::ReactContext m_reactContext;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
} // namespace BuildingBlocksRN
|