-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Expand file tree
/
Copy pathLaunchServer.lua
More file actions
213 lines (196 loc) · 6.08 KB
/
Copy pathLaunchServer.lua
File metadata and controls
213 lines (196 loc) · 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
-- Start a server
local url = ...
local luaSocket = require("socket")
local server = luaSocket.tcp4()
local function bindSocket()
local res, err
res, err = server:bind("localhost", 49082) or server:bind("localhost", 49083) or server:bind("localhost", 49084)
if not res then
server:close()
else
res, err = server:listen(1)
if not res then
server:close()
else
return server
end
end
return nil, err
end
assert(bindSocket())
local host, port = server:getsockname()
ConPrintf("Server started on %s:%s", host, port)
local redirect_uri = string.format(
"http://localhost:%d", port
)
ConPrintf("Redirect URI: %s", redirect_uri)
url = url .. string.format("&redirect_uri=%s", redirect_uri)
local commonResponse = [[
HTTP/1.1 200 OK
Content-Type: text/html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PoB - Authentication Complete</title>
<style>
body {
font-family: Arial, sans-serif;
background: #121212;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
}
.card {
background: #1E1E1E;
padding: 20px;
border-radius: 10px;
box-shadow: 0px 4px 10px rgba(255, 255, 255, 0.1);
width: 90%;
max-width: 400px;
text-align: center;
}
.card h1 {
font-size: 24px;
color: #4CAF50;
margin-bottom: 10px;
}
.card p {
font-size: 18px;
margin-bottom: 15px;
}
.card code {
background: #f8d7da;
color: #721c24;
padding: 3px 6px;
border-radius: 4px;
font-family: monospace;
display: inline-block;
margin-bottom: 10px;
}
.close-button {
padding: 10px 20px;
background: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
transition: background 0.3s;
}
.close-button:hover {
background: #45a049;
}
</style>
</head>
<body>
<div class="container">
<div class="card">
]]
local commonResponseEnd = [[
</div>
</div>
</body>
</html>
]]
ConPrintf("Authorization URL copied to clipboard: %s", url)
Copy(url)
OpenURL(url)
--- Handle an incoming socket connection, to complete an OAuth redirect.
--- @param client table @The socket connection to handle, as returned by `server:accept()`.
--- @param attempt number @The number of attempts made to handle an incoming connection. This is used for logging
--- purposes, since spurious issues can be difficult to identify otherwise.
--- @return boolean shouldRetry @Whether we should wait for another connection. If false, we've successfully responded
--- to a HTTP request. Note that, for the purposes of this function, we don't care whether authorization was *granted*,
--- just that the process itself was completed and the user was redirected as intended.
--- @return string? code @The OAuth authorization code. This is exchanged for an access token and refresh token later.
--- @return string? state @The OAuth state string. This is a sentinel value used to ensure that a request hasn't been
--- forged.
function handleConnection(client, attempt)
local shouldRetry, code, state = true, nil, nil
local request, err = client:receive("*l")
if err then
ConPrintf("Attempt %d to handle incoming connection failed: %s", attempt, err)
elseif request then
local response
local _, _, method, path, version = request:find("^(%S+)%s(%S+)%s(%S+)")
if method ~= "GET" then
ConPrintf(
"Attempt %d to handle incoming connection received an invalid HTTP request: non-GET method %s",
attempt,
method
)
return true
end
local queryParams = {}
for k, v in path:gmatch("([^&=?]+)=([^&=?]+)") do
queryParams[k] = v:gsub("%%(%x%x)", function(hex)
return string.char(tonumber(hex, 16))
end)
end
if queryParams["code"] ~= nil then
response = commonResponse .. [[
<h1>PoB - Authentication Successful</h1>
<p>✅ Your authentication is complete! You can now return to the app.</p>
]] .. commonResponseEnd
code = queryParams["code"]
state = queryParams["state"]
else
response = commonResponse .. [[
<h1>PoB - Authentication Failed</h1>
<p>❌ Authentication failed. Please try again.</p>
<code>]] .. queryParams["error"] .. ": " .. queryParams["error_description"] .. [[</code>
]] .. commonResponseEnd
end
shouldRetry = false
if attempt ~= 1 then
ConPrintf("Attempt %d to handle incoming connection received a valid HTTP request", attempt)
end
-- Send HTTP Response
--ConPrintf("Sending response: %s", response)
client:send(response)
end
return shouldRetry, code, state
end
-- Some software (think VPNs, anything network-related, or even OS services) will occasionally attempt to connect to
-- newly-opened sockets. The OAuth callback server therefore keeps listening for another connection when a request
-- cannot be handled, instead of giving up immediately.
--
-- The server waits for up to 60 seconds, or until it receives a valid OAuth response. The authorization URL is copied
-- to the clipboard before it is opened, so the user can paste it into another browser if needed.
--
-- Connections are still handled one at a time. That is sufficient here because the server only needs one valid
-- callback, while the retry behavior protects it from unrelated connections.
local attempt = 1
local stopAt = os.time() + 60
local errMsg
local shouldRetry, code, state = true, nil, nil
while (os.time() < stopAt) and shouldRetry do
-- `settimeout` applies only to individual operations, but we're more concerned with not spending more than 60
-- seconds *total* waiting, so we adjust with each iteration as necessary.
local remainingTime = math.max(0, stopAt - os.time())
server:settimeout(remainingTime)
local client = server:accept()
if not client then
goto retry
end
client:settimeout(5)
shouldRetry, code, state = handleConnection(client, attempt)
client:close()
:: retry ::
attempt = attempt + 1
end
server:close()
if os.time() >= stopAt then
errMsg = "Timeout reached without a response received by the local server"
end
return code, errMsg, state, port