Add clock drift error messages to remaining 18 implementations
When timestamp auth fails (401 with timestamp in error), show helpful message: - Error: Request timestamp expired (must be within 5 minutes of server time) - Your computer's clock may have drifted. - NTP sync commands for Linux/macOS/Windows Updated: Un.cs, Un.java, un.clj, un.cob, un.erl, un.ex, un.f90, un.forth, un.fs, un.hs, un.lisp, un.m, un.ml, un.pro, un.r, un.raku, un.scm, un.zig All 42 implementations now have clock drift detection.
This commit is contained in:
parent
bc0da08a08
commit
a5a4f23594
18 changed files with 455 additions and 42 deletions
18
Un.cs
18
Un.cs
|
|
@ -605,13 +605,31 @@ class Un
|
||||||
catch (WebException ex)
|
catch (WebException ex)
|
||||||
{
|
{
|
||||||
string error = "";
|
string error = "";
|
||||||
|
int statusCode = 0;
|
||||||
if (ex.Response != null)
|
if (ex.Response != null)
|
||||||
{
|
{
|
||||||
using (StreamReader reader = new StreamReader(ex.Response.GetResponseStream()))
|
using (StreamReader reader = new StreamReader(ex.Response.GetResponseStream()))
|
||||||
{
|
{
|
||||||
error = reader.ReadToEnd();
|
error = reader.ReadToEnd();
|
||||||
}
|
}
|
||||||
|
if (ex.Response is HttpWebResponse httpResponse)
|
||||||
|
{
|
||||||
|
statusCode = (int)httpResponse.StatusCode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for clock drift errors
|
||||||
|
if (error.Contains("timestamp") && (statusCode == 401 || error.ToLower().Contains("expired") || error.ToLower().Contains("invalid")))
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"{RED}Error: Request timestamp expired (must be within 5 minutes of server time){RESET}");
|
||||||
|
Console.Error.WriteLine($"{YELLOW}Your computer's clock may have drifted.{RESET}");
|
||||||
|
Console.Error.WriteLine("Check your system time and sync with NTP if needed:");
|
||||||
|
Console.Error.WriteLine(" Linux: sudo ntpdate -s time.nist.gov");
|
||||||
|
Console.Error.WriteLine(" macOS: sudo sntp -sS time.apple.com");
|
||||||
|
Console.Error.WriteLine(" Windows: w32tm /resync");
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
throw new Exception($"HTTP error - {error}");
|
throw new Exception($"HTTP error - {error}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
Un.java
12
Un.java
|
|
@ -555,6 +555,18 @@ public class Un {
|
||||||
int status = conn.getResponseCode();
|
int status = conn.getResponseCode();
|
||||||
if (status < 200 || status >= 300) {
|
if (status < 200 || status >= 300) {
|
||||||
String error = readStream(conn.getErrorStream());
|
String error = readStream(conn.getErrorStream());
|
||||||
|
|
||||||
|
// Check for clock drift errors
|
||||||
|
if (error.contains("timestamp") && (status == 401 || error.toLowerCase().contains("expired") || error.toLowerCase().contains("invalid"))) {
|
||||||
|
System.err.println(RED + "Error: Request timestamp expired (must be within 5 minutes of server time)" + RESET);
|
||||||
|
System.err.println(YELLOW + "Your computer's clock may have drifted." + RESET);
|
||||||
|
System.err.println("Check your system time and sync with NTP if needed:");
|
||||||
|
System.err.println(" Linux: sudo ntpdate -s time.nist.gov");
|
||||||
|
System.err.println(" macOS: sudo sntp -sS time.apple.com");
|
||||||
|
System.err.println(" Windows: w32tm /resync");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
throw new Exception("HTTP " + status + " - " + error);
|
throw new Exception("HTTP " + status + " - " + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
30
un.clj
30
un.clj
|
|
@ -127,6 +127,22 @@
|
||||||
(let [message (str timestamp ":" method ":" path ":" body)]
|
(let [message (str timestamp ":" method ":" path ":" body)]
|
||||||
(hmac-sha256 secret-key message)))
|
(hmac-sha256 secret-key message)))
|
||||||
|
|
||||||
|
(defn check-clock-drift-error [response]
|
||||||
|
(let [has-timestamp (or (str/includes? response "timestamp")
|
||||||
|
(str/includes? response "\"timestamp\""))
|
||||||
|
has-401 (str/includes? response "401")
|
||||||
|
has-expired (str/includes? response "expired")
|
||||||
|
has-invalid (str/includes? response "invalid")]
|
||||||
|
(when (and has-timestamp (or has-401 has-expired has-invalid))
|
||||||
|
(binding [*out* *err*]
|
||||||
|
(println (str red "Error: Request timestamp expired (must be within 5 minutes of server time)" reset))
|
||||||
|
(println (str yellow "Your computer's clock may have drifted." reset))
|
||||||
|
(println "Check your system time and sync with NTP if needed:")
|
||||||
|
(println " Linux: sudo ntpdate -s time.nist.gov")
|
||||||
|
(println " macOS: sudo sntp -sS time.apple.com")
|
||||||
|
(println " Windows: w32tm /resync"))
|
||||||
|
(System/exit 1))))
|
||||||
|
|
||||||
(defn build-auth-headers [public-key secret-key method path body]
|
(defn build-auth-headers [public-key secret-key method path body]
|
||||||
(if secret-key
|
(if secret-key
|
||||||
(let [timestamp (str (quot (System/currentTimeMillis) 1000))
|
(let [timestamp (str (quot (System/currentTimeMillis) 1000))
|
||||||
|
|
@ -148,6 +164,7 @@
|
||||||
["-d" (str "@" tmp-file)])
|
["-d" (str "@" tmp-file)])
|
||||||
{:keys [out]} (apply sh args)]
|
{:keys [out]} (apply sh args)]
|
||||||
(io/delete-file tmp-file true)
|
(io/delete-file tmp-file true)
|
||||||
|
(check-clock-drift-error out)
|
||||||
out)))
|
out)))
|
||||||
|
|
||||||
(defn curl-get [api-key endpoint]
|
(defn curl-get [api-key endpoint]
|
||||||
|
|
@ -155,16 +172,20 @@
|
||||||
auth-headers (build-auth-headers public-key secret-key "GET" endpoint "")
|
auth-headers (build-auth-headers public-key secret-key "GET" endpoint "")
|
||||||
args (concat ["curl" "-s"
|
args (concat ["curl" "-s"
|
||||||
(str "https://api.unsandbox.com" endpoint)]
|
(str "https://api.unsandbox.com" endpoint)]
|
||||||
auth-headers)]
|
auth-headers)
|
||||||
(:out (apply sh args))))
|
result (:out (apply sh args))]
|
||||||
|
(check-clock-drift-error result)
|
||||||
|
result))
|
||||||
|
|
||||||
(defn curl-delete [api-key endpoint]
|
(defn curl-delete [api-key endpoint]
|
||||||
(let [[public-key secret-key] (get-api-keys)
|
(let [[public-key secret-key] (get-api-keys)
|
||||||
auth-headers (build-auth-headers public-key secret-key "DELETE" endpoint "")
|
auth-headers (build-auth-headers public-key secret-key "DELETE" endpoint "")
|
||||||
args (concat ["curl" "-s" "-X" "DELETE"
|
args (concat ["curl" "-s" "-X" "DELETE"
|
||||||
(str "https://api.unsandbox.com" endpoint)]
|
(str "https://api.unsandbox.com" endpoint)]
|
||||||
auth-headers)]
|
auth-headers)
|
||||||
(:out (apply sh args))))
|
result (:out (apply sh args))]
|
||||||
|
(check-clock-drift-error result)
|
||||||
|
result))
|
||||||
|
|
||||||
(defn curl-portal-post [api-key endpoint json-data]
|
(defn curl-portal-post [api-key endpoint json-data]
|
||||||
(let [tmp-file (str "/tmp/un_clj_portal_" (rand-int 999999) ".json")
|
(let [tmp-file (str "/tmp/un_clj_portal_" (rand-int 999999) ".json")
|
||||||
|
|
@ -178,6 +199,7 @@
|
||||||
["-d" (str "@" tmp-file)])
|
["-d" (str "@" tmp-file)])
|
||||||
{:keys [out]} (apply sh args)]
|
{:keys [out]} (apply sh args)]
|
||||||
(io/delete-file tmp-file true)
|
(io/delete-file tmp-file true)
|
||||||
|
(check-clock-drift-error out)
|
||||||
out)))
|
out)))
|
||||||
|
|
||||||
(defn execute-command [file env-vars artifacts out-dir network vcpu]
|
(defn execute-command [file env-vars artifacts out-dir network vcpu]
|
||||||
|
|
|
||||||
22
un.cob
22
un.cob
|
|
@ -293,14 +293,32 @@
|
||||||
"openssl dgst -sha256 -hmac '"
|
"openssl dgst -sha256 -hmac '"
|
||||||
FUNCTION TRIM(WS-SECRET-KEY)
|
FUNCTION TRIM(WS-SECRET-KEY)
|
||||||
"' | cut -d' ' -f2); "
|
"' | cut -d' ' -f2); "
|
||||||
"curl -s -X POST https://api.unsandbox.com/execute "
|
"RESP=$(curl -s -w '\n%{http_code}' -X POST "
|
||||||
|
"https://api.unsandbox.com/execute "
|
||||||
"-H 'Content-Type: application/json' "
|
"-H 'Content-Type: application/json' "
|
||||||
"-H 'Authorization: Bearer "
|
"-H 'Authorization: Bearer "
|
||||||
FUNCTION TRIM(WS-PUBLIC-KEY)
|
FUNCTION TRIM(WS-PUBLIC-KEY)
|
||||||
"' "
|
"' "
|
||||||
"-H 'X-Timestamp: '$TS "
|
"-H 'X-Timestamp: '$TS "
|
||||||
"-H 'X-Signature: '$SIG "
|
"-H 'X-Signature: '$SIG "
|
||||||
"--data-binary \"$BODY\" -o /tmp/unsandbox_resp.json; "
|
"--data-binary \"$BODY\"); "
|
||||||
|
"HTTP_CODE=$(echo \"$RESP\" | tail -n1); "
|
||||||
|
"BODY=$(echo \"$RESP\" | sed '$d'); "
|
||||||
|
"echo \"$BODY\" > /tmp/unsandbox_resp.json; "
|
||||||
|
"if echo \"$BODY\" | grep -q '\"timestamp\"' && "
|
||||||
|
"(echo \"$HTTP_CODE\" | grep -q '401' || "
|
||||||
|
"echo \"$BODY\" | grep -qi 'expired' || "
|
||||||
|
"echo \"$BODY\" | grep -qi 'invalid'); then "
|
||||||
|
"echo -e '\x1b[31mError: Request timestamp expired "
|
||||||
|
"(must be within 5 minutes of server time)\x1b[0m' >&2; "
|
||||||
|
"echo -e '\x1b[33mYour computer'"'"'s clock may have "
|
||||||
|
"drifted.\x1b[0m' >&2; "
|
||||||
|
"echo 'Check your system time and sync with NTP if "
|
||||||
|
"needed:' >&2; "
|
||||||
|
"echo ' Linux: sudo ntpdate -s time.nist.gov' >&2; "
|
||||||
|
"echo ' macOS: sudo sntp -sS time.apple.com' >&2; "
|
||||||
|
"echo ' Windows: w32tm /resync' >&2; "
|
||||||
|
"rm -f /tmp/unsandbox_resp.json; exit 1; fi; "
|
||||||
"jq -r '.stdout // empty' /tmp/unsandbox_resp.json | "
|
"jq -r '.stdout // empty' /tmp/unsandbox_resp.json | "
|
||||||
"sed 's/^/\x1b[34m/' | sed 's/$/\x1b[0m/'; "
|
"sed 's/^/\x1b[34m/' | sed 's/$/\x1b[0m/'; "
|
||||||
"jq -r '.stderr // empty' /tmp/unsandbox_resp.json | "
|
"jq -r '.stderr // empty' /tmp/unsandbox_resp.json | "
|
||||||
|
|
|
||||||
32
un.erl
32
un.erl
|
|
@ -322,6 +322,25 @@ make_signature(SecretKey, Timestamp, Method, Path, Body) ->
|
||||||
Message = Timestamp ++ ":" ++ Method ++ ":" ++ Path ++ ":" ++ Body,
|
Message = Timestamp ++ ":" ++ Method ++ ":" ++ Path ++ ":" ++ Body,
|
||||||
hmac_sha256(SecretKey, Message).
|
hmac_sha256(SecretKey, Message).
|
||||||
|
|
||||||
|
check_clock_drift_error(Response) ->
|
||||||
|
HasTimestamp = string:str(Response, "timestamp") > 0 orelse string:str(Response, "\"timestamp\"") > 0,
|
||||||
|
Has401 = string:str(Response, "401") > 0,
|
||||||
|
HasExpired = string:str(Response, "expired") > 0,
|
||||||
|
HasInvalid = string:str(Response, "invalid") > 0,
|
||||||
|
|
||||||
|
case HasTimestamp andalso (Has401 orelse HasExpired orelse HasInvalid) of
|
||||||
|
true ->
|
||||||
|
io:format(standard_error, "\033[31mError: Request timestamp expired (must be within 5 minutes of server time)\033[0m~n", []),
|
||||||
|
io:format(standard_error, "\033[33mYour computer's clock may have drifted.\033[0m~n", []),
|
||||||
|
io:format(standard_error, "Check your system time and sync with NTP if needed:~n", []),
|
||||||
|
io:format(standard_error, " Linux: sudo ntpdate -s time.nist.gov~n", []),
|
||||||
|
io:format(standard_error, " macOS: sudo sntp -sS time.apple.com~n", []),
|
||||||
|
io:format(standard_error, " Windows: w32tm /resync~n", []),
|
||||||
|
halt(1);
|
||||||
|
false ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
build_auth_headers(PublicKey, SecretKey, Method, Path, Body) ->
|
build_auth_headers(PublicKey, SecretKey, Method, Path, Body) ->
|
||||||
if
|
if
|
||||||
SecretKey =/= false ->
|
SecretKey =/= false ->
|
||||||
|
|
@ -404,7 +423,9 @@ curl_post(ApiKey, Endpoint, TmpFile) ->
|
||||||
" -H 'Content-Type: application/json'" ++
|
" -H 'Content-Type: application/json'" ++
|
||||||
AuthHeaders ++
|
AuthHeaders ++
|
||||||
" -d @" ++ TmpFile,
|
" -d @" ++ TmpFile,
|
||||||
os:cmd(Cmd).
|
Result = os:cmd(Cmd),
|
||||||
|
check_clock_drift_error(Result),
|
||||||
|
Result.
|
||||||
|
|
||||||
curl_post_portal(ApiKey, Endpoint, Data) ->
|
curl_post_portal(ApiKey, Endpoint, Data) ->
|
||||||
TmpFile = write_temp_file(Data),
|
TmpFile = write_temp_file(Data),
|
||||||
|
|
@ -416,6 +437,7 @@ curl_post_portal(ApiKey, Endpoint, Data) ->
|
||||||
" -d @" ++ TmpFile,
|
" -d @" ++ TmpFile,
|
||||||
Result = os:cmd(Cmd),
|
Result = os:cmd(Cmd),
|
||||||
file:delete(TmpFile),
|
file:delete(TmpFile),
|
||||||
|
check_clock_drift_error(Result),
|
||||||
Result.
|
Result.
|
||||||
|
|
||||||
curl_get(ApiKey, Endpoint) ->
|
curl_get(ApiKey, Endpoint) ->
|
||||||
|
|
@ -423,14 +445,18 @@ curl_get(ApiKey, Endpoint) ->
|
||||||
AuthHeaders = build_auth_headers(PublicKey, SecretKey, "GET", Endpoint, ""),
|
AuthHeaders = build_auth_headers(PublicKey, SecretKey, "GET", Endpoint, ""),
|
||||||
Cmd = "curl -s https://api.unsandbox.com" ++ Endpoint ++
|
Cmd = "curl -s https://api.unsandbox.com" ++ Endpoint ++
|
||||||
AuthHeaders,
|
AuthHeaders,
|
||||||
os:cmd(Cmd).
|
Result = os:cmd(Cmd),
|
||||||
|
check_clock_drift_error(Result),
|
||||||
|
Result.
|
||||||
|
|
||||||
curl_delete(ApiKey, Endpoint) ->
|
curl_delete(ApiKey, Endpoint) ->
|
||||||
{PublicKey, SecretKey} = get_api_keys(),
|
{PublicKey, SecretKey} = get_api_keys(),
|
||||||
AuthHeaders = build_auth_headers(PublicKey, SecretKey, "DELETE", Endpoint, ""),
|
AuthHeaders = build_auth_headers(PublicKey, SecretKey, "DELETE", Endpoint, ""),
|
||||||
Cmd = "curl -s -X DELETE https://api.unsandbox.com" ++ Endpoint ++
|
Cmd = "curl -s -X DELETE https://api.unsandbox.com" ++ Endpoint ++
|
||||||
AuthHeaders,
|
AuthHeaders,
|
||||||
os:cmd(Cmd).
|
Result = os:cmd(Cmd),
|
||||||
|
check_clock_drift_error(Result),
|
||||||
|
Result.
|
||||||
|
|
||||||
%% Argument parsing
|
%% Argument parsing
|
||||||
parse_exec_args([], Opts) ->
|
parse_exec_args([], Opts) ->
|
||||||
|
|
|
||||||
24
un.ex
24
un.ex
|
|
@ -463,6 +463,7 @@ defmodule Un do
|
||||||
{output, _exit} = System.cmd("curl", args, stderr_to_stdout: true)
|
{output, _exit} = System.cmd("curl", args, stderr_to_stdout: true)
|
||||||
|
|
||||||
File.rm(tmp_file)
|
File.rm(tmp_file)
|
||||||
|
check_clock_drift(output)
|
||||||
output
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -497,6 +498,7 @@ defmodule Un do
|
||||||
{output, _exit} = System.cmd("curl", args, stderr_to_stdout: true)
|
{output, _exit} = System.cmd("curl", args, stderr_to_stdout: true)
|
||||||
|
|
||||||
File.rm(tmp_file)
|
File.rm(tmp_file)
|
||||||
|
check_clock_drift(output)
|
||||||
output
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -511,6 +513,7 @@ defmodule Un do
|
||||||
|
|
||||||
{output, _exit} = System.cmd("curl", args, stderr_to_stdout: true)
|
{output, _exit} = System.cmd("curl", args, stderr_to_stdout: true)
|
||||||
|
|
||||||
|
check_clock_drift(output)
|
||||||
output
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -525,6 +528,7 @@ defmodule Un do
|
||||||
|
|
||||||
{output, _exit} = System.cmd("curl", args, stderr_to_stdout: true)
|
{output, _exit} = System.cmd("curl", args, stderr_to_stdout: true)
|
||||||
|
|
||||||
|
check_clock_drift(output)
|
||||||
output
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -554,6 +558,26 @@ defmodule Un do
|
||||||
defp get_opt([_arg | rest], long, short, default) do
|
defp get_opt([_arg | rest], long, short, default) do
|
||||||
get_opt(rest, long, short, default)
|
get_opt(rest, long, short, default)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp check_clock_drift(response) do
|
||||||
|
response_lower = String.downcase(response)
|
||||||
|
|
||||||
|
# Check if response contains "timestamp" and error indicators
|
||||||
|
has_timestamp = String.contains?(response_lower, "timestamp")
|
||||||
|
has_error = String.contains?(response_lower, "401") or
|
||||||
|
String.contains?(response_lower, "expired") or
|
||||||
|
String.contains?(response_lower, "invalid")
|
||||||
|
|
||||||
|
if has_timestamp and has_error do
|
||||||
|
IO.puts(:stderr, "#{@red}Error: Request timestamp expired (must be within 5 minutes of server time)#{@reset}")
|
||||||
|
IO.puts(:stderr, "#{@yellow}Your computer's clock may have drifted.")
|
||||||
|
IO.puts(:stderr, "Check your system time and sync with NTP if needed:")
|
||||||
|
IO.puts(:stderr, " Linux: sudo ntpdate -s time.nist.gov")
|
||||||
|
IO.puts(:stderr, " macOS: sudo sntp -sS time.apple.com")
|
||||||
|
IO.puts(:stderr, " Windows: w32tm /resync#{@reset}")
|
||||||
|
System.halt(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Un.main(System.argv())
|
Un.main(System.argv())
|
||||||
|
|
|
||||||
10
un.f90
10
un.f90
|
|
@ -167,6 +167,16 @@ contains
|
||||||
'-H "X-Timestamp: $TS" ', &
|
'-H "X-Timestamp: $TS" ', &
|
||||||
'-H "X-Signature: $SIG" ', &
|
'-H "X-Signature: $SIG" ', &
|
||||||
'--data-binary "$BODY" -o /tmp/unsandbox_resp.json; ', &
|
'--data-binary "$BODY" -o /tmp/unsandbox_resp.json; ', &
|
||||||
|
'RESP=$(cat /tmp/unsandbox_resp.json); ', &
|
||||||
|
'if echo "$RESP" | grep -q "timestamp" && ', &
|
||||||
|
'(echo "$RESP" | grep -Eq "(401|expired|invalid)"); then ', &
|
||||||
|
'echo -e "\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\x1b[0m" >&2; ', &
|
||||||
|
'echo -e "\x1b[33mYour computer'\''s clock may have drifted.\x1b[0m" >&2; ', &
|
||||||
|
'echo "Check your system time and sync with NTP if needed:" >&2; ', &
|
||||||
|
'echo " Linux: sudo ntpdate -s time.nist.gov" >&2; ', &
|
||||||
|
'echo " macOS: sudo sntp -sS time.apple.com" >&2; ', &
|
||||||
|
'echo -e " Windows: w32tm /resync\x1b[0m" >&2; ', &
|
||||||
|
'rm -f /tmp/unsandbox_resp.json; exit 1; fi; ', &
|
||||||
'jq -r ".stdout // empty" /tmp/unsandbox_resp.json | ', &
|
'jq -r ".stdout // empty" /tmp/unsandbox_resp.json | ', &
|
||||||
'sed "s/^/\x1b[34m/" | sed "s/$/\x1b[0m/"; ', &
|
'sed "s/^/\x1b[34m/" | sed "s/$/\x1b[0m/"; ', &
|
||||||
'jq -r ".stderr // empty" /tmp/unsandbox_resp.json | ', &
|
'jq -r ".stderr // empty" /tmp/unsandbox_resp.json | ', &
|
||||||
|
|
|
||||||
16
un.forth
16
un.forth
|
|
@ -157,7 +157,21 @@
|
||||||
s" TIMESTAMP=$(date +%s)" r@ write-line throw
|
s" TIMESTAMP=$(date +%s)" r@ write-line throw
|
||||||
s" MESSAGE=\"$TIMESTAMP:POST:/execute:$BODY\"" r@ write-line throw
|
s" MESSAGE=\"$TIMESTAMP:POST:/execute:$BODY\"" r@ write-line throw
|
||||||
s" SIGNATURE=$(echo -n \"$MESSAGE\" | openssl dgst -sha256 -hmac \"$SECRET_KEY\" -hex | sed 's/.*= //')" r@ write-line throw
|
s" SIGNATURE=$(echo -n \"$MESSAGE\" | openssl dgst -sha256 -hmac \"$SECRET_KEY\" -hex | sed 's/.*= //')" r@ write-line throw
|
||||||
s" curl -s -X POST https://api.unsandbox.com/execute -H 'Content-Type: application/json' -H \"Authorization: Bearer $PUBLIC_KEY\" -H \"X-Timestamp: $TIMESTAMP\" -H \"X-Signature: $SIGNATURE\" -d \"$BODY\" -o /tmp/unsandbox_resp.json; jq -r '.stdout // empty' /tmp/unsandbox_resp.json | sed 's/^/\\x1b[34m/' | sed 's/$/\\x1b[0m/'; jq -r '.stderr // empty' /tmp/unsandbox_resp.json | sed 's/^/\\x1b[31m/' | sed 's/$/\\x1b[0m/' >&2; rm -f /tmp/unsandbox_resp.json" r@ write-line throw
|
s" curl -s -X POST https://api.unsandbox.com/execute -H 'Content-Type: application/json' -H \"Authorization: Bearer $PUBLIC_KEY\" -H \"X-Timestamp: $TIMESTAMP\" -H \"X-Signature: $SIGNATURE\" -d \"$BODY\" -o /tmp/unsandbox_resp.json" r@ write-line throw
|
||||||
|
s" RESP=$(cat /tmp/unsandbox_resp.json)" r@ write-line throw
|
||||||
|
s" if echo \"$RESP\" | grep -q \"timestamp\" && (echo \"$RESP\" | grep -Eq \"(401|expired|invalid)\"); then" r@ write-line throw
|
||||||
|
s" echo -e '\\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\\x1b[0m' >&2" r@ write-line throw
|
||||||
|
s" echo -e '\\x1b[33mYour computer'\\''s clock may have drifted.\\x1b[0m' >&2" r@ write-line throw
|
||||||
|
s" echo 'Check your system time and sync with NTP if needed:' >&2" r@ write-line throw
|
||||||
|
s" echo ' Linux: sudo ntpdate -s time.nist.gov' >&2" r@ write-line throw
|
||||||
|
s" echo ' macOS: sudo sntp -sS time.apple.com' >&2" r@ write-line throw
|
||||||
|
s" echo -e ' Windows: w32tm /resync\\x1b[0m' >&2" r@ write-line throw
|
||||||
|
s" rm -f /tmp/unsandbox_resp.json" r@ write-line throw
|
||||||
|
s" exit 1" r@ write-line throw
|
||||||
|
s" fi" r@ write-line throw
|
||||||
|
s" jq -r '.stdout // empty' /tmp/unsandbox_resp.json | sed 's/^/\\x1b[34m/' | sed 's/$/\\x1b[0m/'" r@ write-line throw
|
||||||
|
s" jq -r '.stderr // empty' /tmp/unsandbox_resp.json | sed 's/^/\\x1b[31m/' | sed 's/$/\\x1b[0m/' >&2" r@ write-line throw
|
||||||
|
s" rm -f /tmp/unsandbox_resp.json" r@ write-line throw
|
||||||
r> close-file throw
|
r> close-file throw
|
||||||
|
|
||||||
s" chmod +x /tmp/unsandbox_script.sh && /tmp/unsandbox_script.sh && rm -f /tmp/unsandbox_script.sh" system
|
s" chmod +x /tmp/unsandbox_script.sh && /tmp/unsandbox_script.sh && rm -f /tmp/unsandbox_script.sh" system
|
||||||
|
|
|
||||||
22
un.fs
22
un.fs
|
|
@ -279,6 +279,17 @@ let apiRequest (endpoint: string) (method: string) (data: (string * obj) list op
|
||||||
reader.ReadToEnd()
|
reader.ReadToEnd()
|
||||||
else
|
else
|
||||||
ex.Message
|
ex.Message
|
||||||
|
|
||||||
|
// Check for clock drift error
|
||||||
|
if errorMsg.Contains("timestamp") && (errorMsg.Contains("401") || errorMsg.Contains("expired") || errorMsg.Contains("invalid")) then
|
||||||
|
eprintfn "%sError: Request timestamp expired (must be within 5 minutes of server time)%s" red reset
|
||||||
|
eprintfn "%sYour computer's clock may have drifted.%s" yellow reset
|
||||||
|
eprintfn "Check your system time and sync with NTP if needed:"
|
||||||
|
eprintfn " Linux: sudo ntpdate -s time.nist.gov"
|
||||||
|
eprintfn " macOS: sudo sntp -sS time.apple.com"
|
||||||
|
eprintfn " Windows: w32tm /resync%s" reset
|
||||||
|
exit 1
|
||||||
|
|
||||||
failwithf "HTTP error - %s" errorMsg
|
failwithf "HTTP error - %s" errorMsg
|
||||||
|
|
||||||
let cmdExecute (args: Args) =
|
let cmdExecute (args: Args) =
|
||||||
|
|
@ -432,6 +443,17 @@ let cmdKey (args: Args) =
|
||||||
with _ -> body
|
with _ -> body
|
||||||
else
|
else
|
||||||
ex.Message
|
ex.Message
|
||||||
|
|
||||||
|
// Check for clock drift error
|
||||||
|
if errorMsg.Contains("timestamp") && (errorMsg.Contains("401") || errorMsg.Contains("expired") || errorMsg.Contains("invalid")) then
|
||||||
|
eprintfn "%sError: Request timestamp expired (must be within 5 minutes of server time)%s" red reset
|
||||||
|
eprintfn "%sYour computer's clock may have drifted.%s" yellow reset
|
||||||
|
eprintfn "Check your system time and sync with NTP if needed:"
|
||||||
|
eprintfn " Linux: sudo ntpdate -s time.nist.gov"
|
||||||
|
eprintfn " macOS: sudo sntp -sS time.apple.com"
|
||||||
|
eprintfn " Windows: w32tm /resync%s" reset
|
||||||
|
exit 1
|
||||||
|
|
||||||
printfn "Reason: %s" errorMsg
|
printfn "Reason: %s" errorMsg
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
|
|
|
||||||
36
un.hs
36
un.hs
|
|
@ -383,6 +383,28 @@ serviceCommand opts = do
|
||||||
putStrLn $ green ++ "Service created" ++ reset
|
putStrLn $ green ++ "Service created" ++ reset
|
||||||
putStrLn stdout
|
putStrLn stdout
|
||||||
|
|
||||||
|
-- Check for clock drift error
|
||||||
|
checkClockDriftError :: String -> IO ()
|
||||||
|
checkClockDriftError response = do
|
||||||
|
let hasTimestamp = "timestamp" `isPrefixOf` dropWhile (/= 't') response ||
|
||||||
|
"\"timestamp\"" `isInfixOf` response
|
||||||
|
let has401 = "401" `isInfixOf` response
|
||||||
|
let hasExpired = "expired" `isInfixOf` response
|
||||||
|
let hasInvalid = "invalid" `isInfixOf` response
|
||||||
|
|
||||||
|
when (hasTimestamp && (has401 || hasExpired || hasInvalid)) $ do
|
||||||
|
hPutStrLn stderr $ red ++ "Error: Request timestamp expired (must be within 5 minutes of server time)" ++ reset
|
||||||
|
hPutStrLn stderr $ yellow ++ "Your computer's clock may have drifted." ++ reset
|
||||||
|
hPutStrLn stderr "Check your system time and sync with NTP if needed:"
|
||||||
|
hPutStrLn stderr " Linux: sudo ntpdate -s time.nist.gov"
|
||||||
|
hPutStrLn stderr " macOS: sudo sntp -sS time.apple.com"
|
||||||
|
hPutStrLn stderr " Windows: w32tm /resync"
|
||||||
|
exitFailure
|
||||||
|
where
|
||||||
|
isInfixOf needle haystack = any (isPrefixOf needle) (tails haystack)
|
||||||
|
tails [] = [[]]
|
||||||
|
tails s@(_:xs) = s : tails xs
|
||||||
|
|
||||||
-- HTTP helpers using curl
|
-- HTTP helpers using curl
|
||||||
curlPost :: String -> String -> String -> IO (ExitCode, String, String)
|
curlPost :: String -> String -> String -> IO (ExitCode, String, String)
|
||||||
curlPost apiKey url body = do
|
curlPost apiKey url body = do
|
||||||
|
|
@ -395,6 +417,8 @@ curlPost apiKey url body = do
|
||||||
, url
|
, url
|
||||||
, "-H", "Content-Type: application/json"
|
, "-H", "Content-Type: application/json"
|
||||||
] ++ authHeaders ++ ["-d", body]) ""
|
] ++ authHeaders ++ ["-d", body]) ""
|
||||||
|
-- Check for clock drift error
|
||||||
|
checkClockDriftError stdout
|
||||||
return (exitCode, stdout, stderr)
|
return (exitCode, stdout, stderr)
|
||||||
|
|
||||||
curlGet :: String -> String -> IO (ExitCode, String, String)
|
curlGet :: String -> String -> IO (ExitCode, String, String)
|
||||||
|
|
@ -402,16 +426,22 @@ curlGet apiKey url = do
|
||||||
(publicKey, secretKey) <- getApiKeys
|
(publicKey, secretKey) <- getApiKeys
|
||||||
let path = drop (length "https://api.unsandbox.com") url
|
let path = drop (length "https://api.unsandbox.com") url
|
||||||
authHeaders <- buildAuthHeaders publicKey secretKey "GET" path ""
|
authHeaders <- buildAuthHeaders publicKey secretKey "GET" path ""
|
||||||
readProcessWithExitCode "curl"
|
(exitCode, stdout, stderr) <- readProcessWithExitCode "curl"
|
||||||
([ "-s", url ] ++ authHeaders) ""
|
([ "-s", url ] ++ authHeaders) ""
|
||||||
|
-- Check for clock drift error
|
||||||
|
checkClockDriftError stdout
|
||||||
|
return (exitCode, stdout, stderr)
|
||||||
|
|
||||||
curlDelete :: String -> String -> IO (ExitCode, String, String)
|
curlDelete :: String -> String -> IO (ExitCode, String, String)
|
||||||
curlDelete apiKey url = do
|
curlDelete apiKey url = do
|
||||||
(publicKey, secretKey) <- getApiKeys
|
(publicKey, secretKey) <- getApiKeys
|
||||||
let path = drop (length "https://api.unsandbox.com") url
|
let path = drop (length "https://api.unsandbox.com") url
|
||||||
authHeaders <- buildAuthHeaders publicKey secretKey "DELETE" path ""
|
authHeaders <- buildAuthHeaders publicKey secretKey "DELETE" path ""
|
||||||
readProcessWithExitCode "curl"
|
(exitCode, stdout, stderr) <- readProcessWithExitCode "curl"
|
||||||
([ "-s", "-X", "DELETE", url ] ++ authHeaders) ""
|
([ "-s", "-X", "DELETE", url ] ++ authHeaders) ""
|
||||||
|
-- Check for clock drift error
|
||||||
|
checkClockDriftError stdout
|
||||||
|
return (exitCode, stdout, stderr)
|
||||||
|
|
||||||
-- Get API keys from environment
|
-- Get API keys from environment
|
||||||
getApiKeys :: IO (String, Maybe String)
|
getApiKeys :: IO (String, Maybe String)
|
||||||
|
|
@ -615,4 +645,6 @@ curlPostPortal apiKey url body = do
|
||||||
, url
|
, url
|
||||||
, "-H", "Content-Type: application/json"
|
, "-H", "Content-Type: application/json"
|
||||||
] ++ authHeaders ++ ["-d", body]) ""
|
] ++ authHeaders ++ ["-d", body]) ""
|
||||||
|
-- Check for clock drift error
|
||||||
|
checkClockDriftError stdout
|
||||||
return (exitCode, stdout, stderr)
|
return (exitCode, stdout, stderr)
|
||||||
|
|
|
||||||
58
un.lisp
58
un.lisp
|
|
@ -101,40 +101,62 @@
|
||||||
while line do (format out "~a~%" line))
|
while line do (format out "~a~%" line))
|
||||||
(uiop:wait-process process))))
|
(uiop:wait-process process))))
|
||||||
|
|
||||||
|
(defun check-clock-drift (response)
|
||||||
|
"Check if response indicates clock drift error"
|
||||||
|
(when (and (search "timestamp" response)
|
||||||
|
(or (search "401" response)
|
||||||
|
(search "expired" response)
|
||||||
|
(search "invalid" response)))
|
||||||
|
(format t "~aError: Request timestamp expired (must be within 5 minutes of server time)~a~%" *red* *reset*)
|
||||||
|
(format t "~aYour computer's clock may have drifted.~a~%" *yellow* *reset*)
|
||||||
|
(format t "Check your system time and sync with NTP if needed:~%")
|
||||||
|
(format t " Linux: sudo ntpdate -s time.nist.gov~%")
|
||||||
|
(format t " macOS: sudo sntp -sS time.apple.com~%")
|
||||||
|
(format t " Windows: w32tm /resync~a~%" *reset*)
|
||||||
|
(uiop:quit 1)))
|
||||||
|
|
||||||
(defun curl-post (api-key endpoint json-data)
|
(defun curl-post (api-key endpoint json-data)
|
||||||
(let ((tmp-file (write-temp-file json-data)))
|
(let ((tmp-file (write-temp-file json-data)))
|
||||||
(unwind-protect
|
(unwind-protect
|
||||||
(destructuring-bind (public-key secret-key) (get-api-keys)
|
(destructuring-bind (public-key secret-key) (get-api-keys)
|
||||||
(let ((auth-headers (build-auth-headers public-key secret-key "POST" endpoint json-data))
|
(let* ((auth-headers (build-auth-headers public-key secret-key "POST" endpoint json-data))
|
||||||
(base-args (list "curl" "-s" "-X" "POST"
|
(base-args (list "curl" "-s" "-X" "POST"
|
||||||
(format nil "https://api.unsandbox.com~a" endpoint)
|
(format nil "https://api.unsandbox.com~a" endpoint)
|
||||||
"-H" "Content-Type: application/json")))
|
"-H" "Content-Type: application/json"))
|
||||||
(run-curl (append base-args auth-headers (list "-d" (format nil "@~a" tmp-file))))))
|
(response (run-curl (append base-args auth-headers (list "-d" (format nil "@~a" tmp-file))))))
|
||||||
|
(check-clock-drift response)
|
||||||
|
response))
|
||||||
(delete-file tmp-file))))
|
(delete-file tmp-file))))
|
||||||
|
|
||||||
(defun curl-get (api-key endpoint)
|
(defun curl-get (api-key endpoint)
|
||||||
(destructuring-bind (public-key secret-key) (get-api-keys)
|
(destructuring-bind (public-key secret-key) (get-api-keys)
|
||||||
(let ((auth-headers (build-auth-headers public-key secret-key "GET" endpoint ""))
|
(let* ((auth-headers (build-auth-headers public-key secret-key "GET" endpoint ""))
|
||||||
(base-args (list "curl" "-s"
|
(base-args (list "curl" "-s"
|
||||||
(format nil "https://api.unsandbox.com~a" endpoint))))
|
(format nil "https://api.unsandbox.com~a" endpoint)))
|
||||||
(run-curl (append base-args auth-headers)))))
|
(response (run-curl (append base-args auth-headers))))
|
||||||
|
(check-clock-drift response)
|
||||||
|
response)))
|
||||||
|
|
||||||
(defun curl-delete (api-key endpoint)
|
(defun curl-delete (api-key endpoint)
|
||||||
(destructuring-bind (public-key secret-key) (get-api-keys)
|
(destructuring-bind (public-key secret-key) (get-api-keys)
|
||||||
(let ((auth-headers (build-auth-headers public-key secret-key "DELETE" endpoint ""))
|
(let* ((auth-headers (build-auth-headers public-key secret-key "DELETE" endpoint ""))
|
||||||
(base-args (list "curl" "-s" "-X" "DELETE"
|
(base-args (list "curl" "-s" "-X" "DELETE"
|
||||||
(format nil "https://api.unsandbox.com~a" endpoint))))
|
(format nil "https://api.unsandbox.com~a" endpoint)))
|
||||||
(run-curl (append base-args auth-headers)))))
|
(response (run-curl (append base-args auth-headers))))
|
||||||
|
(check-clock-drift response)
|
||||||
|
response)))
|
||||||
|
|
||||||
(defun curl-post-portal (api-key endpoint json-data)
|
(defun curl-post-portal (api-key endpoint json-data)
|
||||||
(let ((tmp-file (write-temp-file json-data)))
|
(let ((tmp-file (write-temp-file json-data)))
|
||||||
(unwind-protect
|
(unwind-protect
|
||||||
(destructuring-bind (public-key secret-key) (get-api-keys)
|
(destructuring-bind (public-key secret-key) (get-api-keys)
|
||||||
(let ((auth-headers (build-auth-headers public-key secret-key "POST" endpoint json-data))
|
(let* ((auth-headers (build-auth-headers public-key secret-key "POST" endpoint json-data))
|
||||||
(base-args (list "curl" "-s" "-X" "POST"
|
(base-args (list "curl" "-s" "-X" "POST"
|
||||||
(format nil "~a~a" *portal-base* endpoint)
|
(format nil "~a~a" *portal-base* endpoint)
|
||||||
"-H" "Content-Type: application/json")))
|
"-H" "Content-Type: application/json"))
|
||||||
(run-curl (append base-args auth-headers (list "-d" (format nil "@~a" tmp-file))))))
|
(response (run-curl (append base-args auth-headers (list "-d" (format nil "@~a" tmp-file))))))
|
||||||
|
(check-clock-drift response)
|
||||||
|
response))
|
||||||
(delete-file tmp-file))))
|
(delete-file tmp-file))))
|
||||||
|
|
||||||
(defun get-api-keys ()
|
(defun get-api-keys ()
|
||||||
|
|
|
||||||
20
un.m
20
un.m
|
|
@ -94,6 +94,24 @@ void getApiKeys(NSString** publicKey, NSString** secretKey) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void checkClockDrift(NSString* response) {
|
||||||
|
NSString* responseLower = [response lowercaseString];
|
||||||
|
if ([responseLower rangeOfString:@"timestamp"].location != NSNotFound &&
|
||||||
|
([responseLower rangeOfString:@"401"].location != NSNotFound ||
|
||||||
|
[responseLower rangeOfString:@"expired"].location != NSNotFound ||
|
||||||
|
[responseLower rangeOfString:@"invalid"].location != NSNotFound)) {
|
||||||
|
fprintf(stderr, "%sError: Request timestamp expired (must be within 5 minutes of server time)%s\n",
|
||||||
|
[RED UTF8String], [RESET UTF8String]);
|
||||||
|
fprintf(stderr, "%sYour computer's clock may have drifted.%s\n",
|
||||||
|
[YELLOW UTF8String], [RESET UTF8String]);
|
||||||
|
fprintf(stderr, "Check your system time and sync with NTP if needed:\n");
|
||||||
|
fprintf(stderr, " Linux: sudo ntpdate -s time.nist.gov\n");
|
||||||
|
fprintf(stderr, " macOS: sudo sntp -sS time.apple.com\n");
|
||||||
|
fprintf(stderr, " Windows: w32tm /resync%s\n", [RESET UTF8String]);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NSString* hmacSha256Hex(NSString* key, NSString* message) {
|
NSString* hmacSha256Hex(NSString* key, NSString* message) {
|
||||||
const char* cKey = [key UTF8String];
|
const char* cKey = [key UTF8String];
|
||||||
const char* cMessage = [message UTF8String];
|
const char* cMessage = [message UTF8String];
|
||||||
|
|
@ -170,6 +188,7 @@ NSDictionary* apiRequest(NSString* endpoint, NSString* method, NSDictionary* dat
|
||||||
if (responseData) {
|
if (responseData) {
|
||||||
NSString* errMsg = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
|
NSString* errMsg = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
|
||||||
fprintf(stderr, "%s\n", [errMsg UTF8String]);
|
fprintf(stderr, "%s\n", [errMsg UTF8String]);
|
||||||
|
checkClockDrift(errMsg);
|
||||||
}
|
}
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
@ -350,6 +369,7 @@ NSDictionary* portalRequest(NSString* endpoint, NSString* method, NSDictionary*
|
||||||
if (responseData) {
|
if (responseData) {
|
||||||
NSString* errMsg = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
|
NSString* errMsg = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
|
||||||
fprintf(stderr, "%s\n", [errMsg UTF8String]);
|
fprintf(stderr, "%s\n", [errMsg UTF8String]);
|
||||||
|
checkClockDrift(errMsg);
|
||||||
}
|
}
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
un.ml
29
un.ml
|
|
@ -112,6 +112,31 @@ let escape_json s =
|
||||||
) s;
|
) s;
|
||||||
Buffer.contents buf
|
Buffer.contents buf
|
||||||
|
|
||||||
|
(* Check for clock drift errors *)
|
||||||
|
let check_clock_drift response =
|
||||||
|
let response_lower = String.lowercase_ascii response in
|
||||||
|
let contains_substring s sub =
|
||||||
|
try
|
||||||
|
let _ = Str.search_forward (Str.regexp_string sub) s 0 in
|
||||||
|
true
|
||||||
|
with Not_found -> false
|
||||||
|
in
|
||||||
|
let has_timestamp = contains_substring response_lower "timestamp" in
|
||||||
|
let has_401 = contains_substring response_lower "401" in
|
||||||
|
let has_expired = contains_substring response_lower "expired" in
|
||||||
|
let has_invalid = contains_substring response_lower "invalid" in
|
||||||
|
let has_error = has_401 || has_expired || has_invalid in
|
||||||
|
|
||||||
|
if has_timestamp && has_error then begin
|
||||||
|
Printf.fprintf stderr "%sError: Request timestamp expired (must be within 5 minutes of server time)%s\n" red reset;
|
||||||
|
Printf.fprintf stderr "%sYour computer's clock may have drifted.\n" yellow;
|
||||||
|
Printf.fprintf stderr "Check your system time and sync with NTP if needed:\n";
|
||||||
|
Printf.fprintf stderr " Linux: sudo ntpdate -s time.nist.gov\n";
|
||||||
|
Printf.fprintf stderr " macOS: sudo sntp -sS time.apple.com\n";
|
||||||
|
Printf.fprintf stderr " Windows: w32tm /resync%s\n" reset;
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
(* Execute curl command *)
|
(* Execute curl command *)
|
||||||
let curl_post api_key endpoint json =
|
let curl_post api_key endpoint json =
|
||||||
let (public_key, secret_key) = get_api_keys () in
|
let (public_key, secret_key) = get_api_keys () in
|
||||||
|
|
@ -130,6 +155,7 @@ let curl_post api_key endpoint json =
|
||||||
let output = read_all "" in
|
let output = read_all "" in
|
||||||
let _ = Unix.close_process_in ic in
|
let _ = Unix.close_process_in ic in
|
||||||
Sys.remove tmp_file;
|
Sys.remove tmp_file;
|
||||||
|
check_clock_drift output;
|
||||||
output
|
output
|
||||||
|
|
||||||
let portal_curl_post api_key endpoint json =
|
let portal_curl_post api_key endpoint json =
|
||||||
|
|
@ -149,6 +175,7 @@ let portal_curl_post api_key endpoint json =
|
||||||
let output = read_all "" in
|
let output = read_all "" in
|
||||||
let _ = Unix.close_process_in ic in
|
let _ = Unix.close_process_in ic in
|
||||||
Sys.remove tmp_file;
|
Sys.remove tmp_file;
|
||||||
|
check_clock_drift output;
|
||||||
output
|
output
|
||||||
|
|
||||||
let curl_get api_key endpoint =
|
let curl_get api_key endpoint =
|
||||||
|
|
@ -165,6 +192,7 @@ let curl_get api_key endpoint =
|
||||||
in
|
in
|
||||||
let output = read_all "" in
|
let output = read_all "" in
|
||||||
let _ = Unix.close_process_in ic in
|
let _ = Unix.close_process_in ic in
|
||||||
|
check_clock_drift output;
|
||||||
output
|
output
|
||||||
|
|
||||||
let curl_delete api_key endpoint =
|
let curl_delete api_key endpoint =
|
||||||
|
|
@ -181,6 +209,7 @@ let curl_delete api_key endpoint =
|
||||||
in
|
in
|
||||||
let output = read_all "" in
|
let output = read_all "" in
|
||||||
let _ = Unix.close_process_in ic in
|
let _ = Unix.close_process_in ic in
|
||||||
|
check_clock_drift output;
|
||||||
output
|
output
|
||||||
|
|
||||||
(* Extract JSON value - simple regex-based parser *)
|
(* Extract JSON value - simple regex-based parser *)
|
||||||
|
|
|
||||||
10
un.pro
10
un.pro
|
|
@ -128,7 +128,7 @@ execute_file(Filename) :-
|
||||||
|
|
||||||
% Build and execute curl command with HMAC
|
% Build and execute curl command with HMAC
|
||||||
format(atom(Cmd),
|
format(atom(Cmd),
|
||||||
'BODY=$(jq -Rs \'\'\''{language: "~w", code: .}\'\'\'\' < "~w"); TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/execute:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'\'\'s/.*= //\'\'\'\'); curl -s -X POST https://api.unsandbox.com/execute -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" -o /tmp/unsandbox_resp.json; jq -r ".stdout // empty" /tmp/unsandbox_resp.json | sed "s/^/\\x1b[34m/" | sed "s/$/\\x1b[0m/"; jq -r ".stderr // empty" /tmp/unsandbox_resp.json | sed "s/^/\\x1b[31m/" | sed "s/$/\\x1b[0m/" >&2; rm -f /tmp/unsandbox_resp.json',
|
'BODY=$(jq -Rs \'\'\''{language: "~w", code: .}\'\'\'\' < "~w"); TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/execute:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'\'\'s/.*= //\'\'\'\'); curl -s -X POST https://api.unsandbox.com/execute -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" -o /tmp/unsandbox_resp.json; RESP=$(cat /tmp/unsandbox_resp.json); if echo "$RESP" | grep -qi "timestamp" && echo "$RESP" | grep -Eqi "(401|expired|invalid)"; then echo -e "\\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\\x1b[0m" >&2; echo -e "\\x1b[33mYour computer'\''s clock may have drifted.\\x1b[0m" >&2; echo "Check your system time and sync with NTP if needed:" >&2; echo " Linux: sudo ntpdate -s time.nist.gov" >&2; echo " macOS: sudo sntp -sS time.apple.com" >&2; echo " Windows: w32tm /resync\\x1b[0m" >&2; rm -f /tmp/unsandbox_resp.json; exit 1; fi; jq -r ".stdout // empty" /tmp/unsandbox_resp.json | sed "s/^/\\x1b[34m/" | sed "s/$/\\x1b[0m/"; jq -r ".stderr // empty" /tmp/unsandbox_resp.json | sed "s/^/\\x1b[31m/" | sed "s/$/\\x1b[0m/" >&2; rm -f /tmp/unsandbox_resp.json',
|
||||||
[Language, Filename, SecretKey, PublicKey]),
|
[Language, Filename, SecretKey, PublicKey]),
|
||||||
shell(Cmd, 0).
|
shell(Cmd, 0).
|
||||||
|
|
||||||
|
|
@ -137,7 +137,7 @@ session_list :-
|
||||||
get_public_key(PublicKey),
|
get_public_key(PublicKey),
|
||||||
get_secret_key(SecretKey),
|
get_secret_key(SecretKey),
|
||||||
format(atom(Cmd),
|
format(atom(Cmd),
|
||||||
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:GET:/sessions:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X GET https://api.unsandbox.com/sessions -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" | jq -r \'.sessions[] | "\\(.id) \\(.shell) \\(.status) \\(.created_at)"\' 2>/dev/null || echo "No active sessions"',
|
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:GET:/sessions:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP=$(curl -s -X GET https://api.unsandbox.com/sessions -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE"); if echo "$RESP" | grep -qi "timestamp" && echo "$RESP" | grep -Eqi "(401|expired|invalid)"; then echo -e "\\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\\x1b[0m" >&2; echo -e "\\x1b[33mYour computer'\''s clock may have drifted.\\x1b[0m" >&2; echo "Check your system time and sync with NTP if needed:" >&2; echo " Linux: sudo ntpdate -s time.nist.gov" >&2; echo " macOS: sudo sntp -sS time.apple.com" >&2; echo " Windows: w32tm /resync\\x1b[0m" >&2; exit 1; fi; echo "$RESP" | jq -r \'.sessions[] | "\\(.id) \\(.shell) \\(.status) \\(.created_at)"\' 2>/dev/null || echo "No active sessions"',
|
||||||
[SecretKey, PublicKey]),
|
[SecretKey, PublicKey]),
|
||||||
shell(Cmd, 0).
|
shell(Cmd, 0).
|
||||||
|
|
||||||
|
|
@ -155,7 +155,7 @@ service_list :-
|
||||||
get_public_key(PublicKey),
|
get_public_key(PublicKey),
|
||||||
get_secret_key(SecretKey),
|
get_secret_key(SecretKey),
|
||||||
format(atom(Cmd),
|
format(atom(Cmd),
|
||||||
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:GET:/services:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X GET https://api.unsandbox.com/services -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" | jq -r \'.services[] | "\\(.id) \\(.name) \\(.status)"\' 2>/dev/null || echo "No services"',
|
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:GET:/services:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP=$(curl -s -X GET https://api.unsandbox.com/services -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE"); if echo "$RESP" | grep -qi "timestamp" && echo "$RESP" | grep -Eqi "(401|expired|invalid)"; then echo -e "\\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\\x1b[0m" >&2; echo -e "\\x1b[33mYour computer'\''s clock may have drifted.\\x1b[0m" >&2; echo "Check your system time and sync with NTP if needed:" >&2; echo " Linux: sudo ntpdate -s time.nist.gov" >&2; echo " macOS: sudo sntp -sS time.apple.com" >&2; echo " Windows: w32tm /resync\\x1b[0m" >&2; exit 1; fi; echo "$RESP" | jq -r \'.services[] | "\\(.id) \\(.name) \\(.status)"\' 2>/dev/null || echo "No services"',
|
||||||
[SecretKey, PublicKey]),
|
[SecretKey, PublicKey]),
|
||||||
shell(Cmd, 0).
|
shell(Cmd, 0).
|
||||||
|
|
||||||
|
|
@ -252,11 +252,11 @@ validate_key(Extend) :-
|
||||||
( Extend = true
|
( Extend = true
|
||||||
-> % Build command for --extend mode
|
-> % Build command for --extend mode
|
||||||
format(atom(Cmd),
|
format(atom(Cmd),
|
||||||
'BODY=\'\'{}\'\'; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/keys/validate:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP=$(curl -s -X POST ~w/keys/validate -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY"); PUBLIC_KEY=$(echo "$RESP" | jq -r ".public_key // \\"N/A\\""); xdg-open "~w/keys/extend?pk=$PUBLIC_KEY" 2>/dev/null',
|
'BODY=\'\'{}\'\'; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/keys/validate:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP=$(curl -s -X POST ~w/keys/validate -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY"); if echo "$RESP" | grep -qi "timestamp" && echo "$RESP" | grep -Eqi "(401|expired|invalid)"; then echo -e "\\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\\x1b[0m" >&2; echo -e "\\x1b[33mYour computer'\''s clock may have drifted.\\x1b[0m" >&2; echo "Check your system time and sync with NTP if needed:" >&2; echo " Linux: sudo ntpdate -s time.nist.gov" >&2; echo " macOS: sudo sntp -sS time.apple.com" >&2; echo " Windows: w32tm /resync\\x1b[0m" >&2; exit 1; fi; PUBLIC_KEY=$(echo "$RESP" | jq -r ".public_key // \\"N/A\\""); xdg-open "~w/keys/extend?pk=$PUBLIC_KEY" 2>/dev/null',
|
||||||
[SecretKey, PortalBase, PublicKey, PortalBase])
|
[SecretKey, PortalBase, PublicKey, PortalBase])
|
||||||
; % Build command for normal validation
|
; % Build command for normal validation
|
||||||
format(atom(Cmd),
|
format(atom(Cmd),
|
||||||
'BODY=\'\'{}\'\'; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/keys/validate:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X POST ~w/keys/validate -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" -o /tmp/unsandbox_key_resp.json; STATUS=$?; if [ $STATUS -ne 0 ]; then echo -e "\\x1b[31mInvalid\\x1b[0m"; exit 1; fi; EXPIRED=$(jq -r ".expired // false" /tmp/unsandbox_key_resp.json); if [ "$EXPIRED" = "true" ]; then echo -e "\\x1b[31mExpired\\x1b[0m"; echo "Public Key: $(jq -r ".public_key // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Tier: $(jq -r ".tier // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Expired: $(jq -r ".expires_at // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo -e "\\x1b[33mTo renew: Visit https://unsandbox.com/keys/extend\\x1b[0m"; rm -f /tmp/unsandbox_key_resp.json; exit 1; else echo -e "\\x1b[32mValid\\x1b[0m"; echo "Public Key: $(jq -r ".public_key // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Tier: $(jq -r ".tier // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Status: $(jq -r ".status // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Expires: $(jq -r ".expires_at // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Time Remaining: $(jq -r ".time_remaining // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Rate Limit: $(jq -r ".rate_limit // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Burst: $(jq -r ".burst // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Concurrency: $(jq -r ".concurrency // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; fi; rm -f /tmp/unsandbox_key_resp.json',
|
'BODY=\'\'{}\'\'; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/keys/validate:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X POST ~w/keys/validate -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" -o /tmp/unsandbox_key_resp.json; STATUS=$?; if [ $STATUS -ne 0 ]; then echo -e "\\x1b[31mInvalid\\x1b[0m"; exit 1; fi; RESP=$(cat /tmp/unsandbox_key_resp.json); if echo "$RESP" | grep -qi "timestamp" && echo "$RESP" | grep -Eqi "(401|expired|invalid)"; then echo -e "\\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\\x1b[0m" >&2; echo -e "\\x1b[33mYour computer'\''s clock may have drifted.\\x1b[0m" >&2; echo "Check your system time and sync with NTP if needed:" >&2; echo " Linux: sudo ntpdate -s time.nist.gov" >&2; echo " macOS: sudo sntp -sS time.apple.com" >&2; echo " Windows: w32tm /resync\\x1b[0m" >&2; rm -f /tmp/unsandbox_key_resp.json; exit 1; fi; EXPIRED=$(jq -r ".expired // false" /tmp/unsandbox_key_resp.json); if [ "$EXPIRED" = "true" ]; then echo -e "\\x1b[31mExpired\\x1b[0m"; echo "Public Key: $(jq -r ".public_key // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Tier: $(jq -r ".tier // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Expired: $(jq -r ".expires_at // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo -e "\\x1b[33mTo renew: Visit https://unsandbox.com/keys/extend\\x1b[0m"; rm -f /tmp/unsandbox_key_resp.json; exit 1; else echo -e "\\x1b[32mValid\\x1b[0m"; echo "Public Key: $(jq -r ".public_key // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Tier: $(jq -r ".tier // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Status: $(jq -r ".status // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Expires: $(jq -r ".expires_at // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Time Remaining: $(jq -r ".time_remaining // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Rate Limit: $(jq -r ".rate_limit // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Burst: $(jq -r ".burst // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Concurrency: $(jq -r ".concurrency // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; fi; rm -f /tmp/unsandbox_key_resp.json',
|
||||||
[SecretKey, PortalBase, PublicKey])
|
[SecretKey, PortalBase, PublicKey])
|
||||||
),
|
),
|
||||||
shell(Cmd, 0).
|
shell(Cmd, 0).
|
||||||
|
|
|
||||||
31
un.r
31
un.r
|
|
@ -93,6 +93,25 @@ get_api_keys <- function(args_key = NULL) {
|
||||||
return(list(public_key = public_key, secret_key = secret_key))
|
return(list(public_key = public_key, secret_key = secret_key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_clock_drift <- function(response_text) {
|
||||||
|
response_lower <- tolower(response_text)
|
||||||
|
has_timestamp <- grepl("timestamp", response_lower, fixed = TRUE)
|
||||||
|
has_401 <- grepl("401", response_lower, fixed = TRUE)
|
||||||
|
has_expired <- grepl("expired", response_lower, fixed = TRUE)
|
||||||
|
has_invalid <- grepl("invalid", response_lower, fixed = TRUE)
|
||||||
|
has_error <- has_401 || has_expired || has_invalid
|
||||||
|
|
||||||
|
if (has_timestamp && has_error) {
|
||||||
|
cat(sprintf("%sError: Request timestamp expired (must be within 5 minutes of server time)%s\n", RED, RESET), file = stderr())
|
||||||
|
cat(sprintf("%sYour computer's clock may have drifted.\n", YELLOW), file = stderr())
|
||||||
|
cat("Check your system time and sync with NTP if needed:\n", file = stderr())
|
||||||
|
cat(" Linux: sudo ntpdate -s time.nist.gov\n", file = stderr())
|
||||||
|
cat(" macOS: sudo sntp -sS time.apple.com\n", file = stderr())
|
||||||
|
cat(sprintf(" Windows: w32tm /resync%s\n", RESET), file = stderr())
|
||||||
|
quit(status = 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
api_request <- function(endpoint, public_key, secret_key, method = "GET", data = NULL) {
|
api_request <- function(endpoint, public_key, secret_key, method = "GET", data = NULL) {
|
||||||
url <- paste0(API_BASE, endpoint)
|
url <- paste0(API_BASE, endpoint)
|
||||||
headers <- add_headers(
|
headers <- add_headers(
|
||||||
|
|
@ -129,7 +148,9 @@ api_request <- function(endpoint, public_key, secret_key, method = "GET", data =
|
||||||
stop(paste("Unsupported method:", method))
|
stop(paste("Unsupported method:", method))
|
||||||
}
|
}
|
||||||
|
|
||||||
result <- fromJSON(content(response, "text", encoding = "UTF-8"))
|
response_text <- content(response, "text", encoding = "UTF-8")
|
||||||
|
check_clock_drift(response_text)
|
||||||
|
result <- fromJSON(response_text)
|
||||||
return(result)
|
return(result)
|
||||||
}, error = function(e) {
|
}, error = function(e) {
|
||||||
cat(sprintf("%sError: Request failed: %s%s\n", RED, e$message, RESET), file = stderr())
|
cat(sprintf("%sError: Request failed: %s%s\n", RED, e$message, RESET), file = stderr())
|
||||||
|
|
@ -292,7 +313,9 @@ cmd_key <- function(args) {
|
||||||
|
|
||||||
tryCatch({
|
tryCatch({
|
||||||
response <- POST(url, headers, encode = "json", timeout(10))
|
response <- POST(url, headers, encode = "json", timeout(10))
|
||||||
result <- fromJSON(content(response, "text", encoding = "UTF-8"))
|
response_text <- content(response, "text", encoding = "UTF-8")
|
||||||
|
check_clock_drift(response_text)
|
||||||
|
result <- fromJSON(response_text)
|
||||||
|
|
||||||
if (!is.null(result$public_key)) {
|
if (!is.null(result$public_key)) {
|
||||||
extend_url <- paste0(PORTAL_BASE, "/keys/extend?pk=", result$public_key)
|
extend_url <- paste0(PORTAL_BASE, "/keys/extend?pk=", result$public_key)
|
||||||
|
|
@ -331,7 +354,9 @@ cmd_key <- function(args) {
|
||||||
|
|
||||||
tryCatch({
|
tryCatch({
|
||||||
response <- POST(url, headers, encode = "json", timeout(10))
|
response <- POST(url, headers, encode = "json", timeout(10))
|
||||||
result <- fromJSON(content(response, "text", encoding = "UTF-8"))
|
response_text <- content(response, "text", encoding = "UTF-8")
|
||||||
|
check_clock_drift(response_text)
|
||||||
|
result <- fromJSON(response_text)
|
||||||
|
|
||||||
status <- if (!is.null(result$status)) result$status else "Unknown"
|
status <- if (!is.null(result$status)) result$status else "Unknown"
|
||||||
|
|
||||||
|
|
|
||||||
11
un.raku
11
un.raku
|
|
@ -148,6 +148,17 @@ sub api-request(Str $endpoint, Str $method, %data?, Str :$public-key!, Str :$sec
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Check for clock drift errors
|
||||||
|
if $body.contains('timestamp') && ($body.contains('401') || $body.contains('expired') || $body.contains('invalid')) {
|
||||||
|
note "{$RED}Error: Request timestamp expired (must be within 5 minutes of server time){$RESET}";
|
||||||
|
note "{$YELLOW}Your computer's clock may have drifted.{$RESET}";
|
||||||
|
note "{$YELLOW}Check your system time and sync with NTP if needed:{$RESET}";
|
||||||
|
note "{$YELLOW} Linux: sudo ntpdate -s time.nist.gov{$RESET}";
|
||||||
|
note "{$YELLOW} macOS: sudo sntp -sS time.apple.com{$RESET}";
|
||||||
|
note "{$YELLOW} Windows: w32tm /resync{$RESET}";
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
|
||||||
return from-json($body);
|
return from-json($body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
48
un.scm
48
un.scm
|
|
@ -117,6 +117,18 @@
|
||||||
(loop (cons line lines)))))))
|
(loop (cons line lines)))))))
|
||||||
(close-pipe port)
|
(close-pipe port)
|
||||||
(delete-file tmp-file)
|
(delete-file tmp-file)
|
||||||
|
;; Check for clock drift errors
|
||||||
|
(when (and (string-contains output "timestamp")
|
||||||
|
(or (string-contains output "401")
|
||||||
|
(string-contains output "expired")
|
||||||
|
(string-contains output "invalid")))
|
||||||
|
(format (current-error-port) "~aError: Request timestamp expired (must be within 5 minutes of server time)~a\n" red reset)
|
||||||
|
(format (current-error-port) "~aYour computer's clock may have drifted.~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~aCheck your system time and sync with NTP if needed:~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~a Linux: sudo ntpdate -s time.nist.gov~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~a macOS: sudo sntp -sS time.apple.com~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~a Windows: w32tm /resync~a\n" yellow reset)
|
||||||
|
(exit 1))
|
||||||
output))
|
output))
|
||||||
|
|
||||||
(define (curl-get api-key endpoint)
|
(define (curl-get api-key endpoint)
|
||||||
|
|
@ -133,6 +145,18 @@
|
||||||
(string-join (reverse lines) "\n")
|
(string-join (reverse lines) "\n")
|
||||||
(loop (cons line lines)))))))
|
(loop (cons line lines)))))))
|
||||||
(close-pipe port)
|
(close-pipe port)
|
||||||
|
;; Check for clock drift errors
|
||||||
|
(when (and (string-contains output "timestamp")
|
||||||
|
(or (string-contains output "401")
|
||||||
|
(string-contains output "expired")
|
||||||
|
(string-contains output "invalid")))
|
||||||
|
(format (current-error-port) "~aError: Request timestamp expired (must be within 5 minutes of server time)~a\n" red reset)
|
||||||
|
(format (current-error-port) "~aYour computer's clock may have drifted.~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~aCheck your system time and sync with NTP if needed:~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~a Linux: sudo ntpdate -s time.nist.gov~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~a macOS: sudo sntp -sS time.apple.com~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~a Windows: w32tm /resync~a\n" yellow reset)
|
||||||
|
(exit 1))
|
||||||
output))
|
output))
|
||||||
|
|
||||||
(define (curl-delete api-key endpoint)
|
(define (curl-delete api-key endpoint)
|
||||||
|
|
@ -149,6 +173,18 @@
|
||||||
(string-join (reverse lines) "\n")
|
(string-join (reverse lines) "\n")
|
||||||
(loop (cons line lines)))))))
|
(loop (cons line lines)))))))
|
||||||
(close-pipe port)
|
(close-pipe port)
|
||||||
|
;; Check for clock drift errors
|
||||||
|
(when (and (string-contains output "timestamp")
|
||||||
|
(or (string-contains output "401")
|
||||||
|
(string-contains output "expired")
|
||||||
|
(string-contains output "invalid")))
|
||||||
|
(format (current-error-port) "~aError: Request timestamp expired (must be within 5 minutes of server time)~a\n" red reset)
|
||||||
|
(format (current-error-port) "~aYour computer's clock may have drifted.~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~aCheck your system time and sync with NTP if needed:~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~a Linux: sudo ntpdate -s time.nist.gov~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~a macOS: sudo sntp -sS time.apple.com~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~a Windows: w32tm /resync~a\n" yellow reset)
|
||||||
|
(exit 1))
|
||||||
output))
|
output))
|
||||||
|
|
||||||
(define (curl-post-portal api-key endpoint json-data)
|
(define (curl-post-portal api-key endpoint json-data)
|
||||||
|
|
@ -169,6 +205,18 @@
|
||||||
(loop (cons line lines)))))))
|
(loop (cons line lines)))))))
|
||||||
(close-pipe port)
|
(close-pipe port)
|
||||||
(delete-file tmp-file)
|
(delete-file tmp-file)
|
||||||
|
;; Check for clock drift errors
|
||||||
|
(when (and (string-contains output "timestamp")
|
||||||
|
(or (string-contains output "401")
|
||||||
|
(string-contains output "expired")
|
||||||
|
(string-contains output "invalid")))
|
||||||
|
(format (current-error-port) "~aError: Request timestamp expired (must be within 5 minutes of server time)~a\n" red reset)
|
||||||
|
(format (current-error-port) "~aYour computer's clock may have drifted.~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~aCheck your system time and sync with NTP if needed:~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~a Linux: sudo ntpdate -s time.nist.gov~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~a macOS: sudo sntp -sS time.apple.com~a\n" yellow reset)
|
||||||
|
(format (current-error-port) "~a Windows: w32tm /resync~a\n" yellow reset)
|
||||||
|
(exit 1))
|
||||||
output))
|
output))
|
||||||
|
|
||||||
(define (get-api-keys)
|
(define (get-api-keys)
|
||||||
|
|
|
||||||
68
un.zig
68
un.zig
|
|
@ -362,6 +362,21 @@ pub fn main() !u8 {
|
||||||
defer allocator.free(json_content);
|
defer allocator.free(json_content);
|
||||||
std.fs.cwd().deleteFile(json_file) catch {};
|
std.fs.cwd().deleteFile(json_file) catch {};
|
||||||
|
|
||||||
|
// Check for clock drift errors
|
||||||
|
if (mem.indexOf(u8, json_content, "timestamp") != null and
|
||||||
|
(mem.indexOf(u8, json_content, "401") != null or
|
||||||
|
mem.indexOf(u8, json_content, "expired") != null or
|
||||||
|
mem.indexOf(u8, json_content, "invalid") != null))
|
||||||
|
{
|
||||||
|
std.debug.print("\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33mYour computer's clock may have drifted.\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33mCheck your system time and sync with NTP if needed:\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33m Linux: sudo ntpdate -s time.nist.gov\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33m macOS: sudo sntp -sS time.apple.com\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33m Windows: w32tm /resync\x1b[0m\n", .{});
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Simple JSON parsing to find public_key (looking for "public_key":"value")
|
// Simple JSON parsing to find public_key (looking for "public_key":"value")
|
||||||
const pk_prefix = "\"public_key\":\"";
|
const pk_prefix = "\"public_key\":\"";
|
||||||
var public_key_value: ?[]const u8 = null;
|
var public_key_value: ?[]const u8 = null;
|
||||||
|
|
@ -401,6 +416,21 @@ pub fn main() !u8 {
|
||||||
defer allocator.free(json_content);
|
defer allocator.free(json_content);
|
||||||
std.fs.cwd().deleteFile(json_file) catch {};
|
std.fs.cwd().deleteFile(json_file) catch {};
|
||||||
|
|
||||||
|
// Check for clock drift errors
|
||||||
|
if (mem.indexOf(u8, json_content, "timestamp") != null and
|
||||||
|
(mem.indexOf(u8, json_content, "401") != null or
|
||||||
|
mem.indexOf(u8, json_content, "expired") != null or
|
||||||
|
mem.indexOf(u8, json_content, "invalid") != null))
|
||||||
|
{
|
||||||
|
std.debug.print("\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33mYour computer's clock may have drifted.\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33mCheck your system time and sync with NTP if needed:\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33m Linux: sudo ntpdate -s time.nist.gov\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33m macOS: sudo sntp -sS time.apple.com\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33m Windows: w32tm /resync\x1b[0m\n", .{});
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Simple JSON parsing (looking for specific fields)
|
// Simple JSON parsing (looking for specific fields)
|
||||||
const status_prefix = "\"status\":\"";
|
const status_prefix = "\"status\":\"";
|
||||||
var status: ?[]const u8 = null;
|
var status: ?[]const u8 = null;
|
||||||
|
|
@ -535,14 +565,44 @@ pub fn main() !u8 {
|
||||||
// Execute with curl
|
// Execute with curl
|
||||||
const auth_headers = try buildAuthCmd(allocator, "POST", "/execute", json_content, public_key, secret_key);
|
const auth_headers = try buildAuthCmd(allocator, "POST", "/execute", json_content, public_key, secret_key);
|
||||||
defer allocator.free(auth_headers);
|
defer allocator.free(auth_headers);
|
||||||
const cmd = try std.fmt.allocPrint(allocator, "curl -s -X POST '{s}/execute' -H 'Content-Type: application/json' {s} -d @{s}", .{ API_BASE, auth_headers, json_file });
|
const response_file = "/tmp/unsandbox_response.json";
|
||||||
|
const cmd = try std.fmt.allocPrint(allocator, "curl -s -X POST '{s}/execute' -H 'Content-Type: application/json' {s} -d @{s} -o {s}", .{ API_BASE, auth_headers, json_file, response_file });
|
||||||
defer allocator.free(cmd);
|
defer allocator.free(cmd);
|
||||||
|
|
||||||
const result = std.c.system(cmd.ptr);
|
_ = std.c.system(cmd.ptr);
|
||||||
std.debug.print("\n", .{});
|
|
||||||
|
// Read response to check for clock drift errors
|
||||||
|
const response_content = fs.cwd().readFileAlloc(allocator, response_file, 10 * 1024 * 1024) catch |err| {
|
||||||
|
std.debug.print("\x1b[31mError reading response: {}\x1b[0m\n", .{err});
|
||||||
|
std.fs.cwd().deleteFile(json_file) catch {};
|
||||||
|
std.fs.cwd().deleteFile(response_file) catch {};
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
defer allocator.free(response_content);
|
||||||
|
|
||||||
|
// Check for clock drift errors
|
||||||
|
if (mem.indexOf(u8, response_content, "timestamp") != null and
|
||||||
|
(mem.indexOf(u8, response_content, "401") != null or
|
||||||
|
mem.indexOf(u8, response_content, "expired") != null or
|
||||||
|
mem.indexOf(u8, response_content, "invalid") != null))
|
||||||
|
{
|
||||||
|
std.debug.print("\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33mYour computer's clock may have drifted.\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33mCheck your system time and sync with NTP if needed:\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33m Linux: sudo ntpdate -s time.nist.gov\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33m macOS: sudo sntp -sS time.apple.com\x1b[0m\n", .{});
|
||||||
|
std.debug.print("\x1b[33m Windows: w32tm /resync\x1b[0m\n", .{});
|
||||||
|
std.fs.cwd().deleteFile(json_file) catch {};
|
||||||
|
std.fs.cwd().deleteFile(response_file) catch {};
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print response
|
||||||
|
std.debug.print("{s}\n", .{response_content});
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
std.fs.cwd().deleteFile(json_file) catch {};
|
std.fs.cwd().deleteFile(json_file) catch {};
|
||||||
|
std.fs.cwd().deleteFile(response_file) catch {};
|
||||||
|
|
||||||
return if (result == 0) 0 else 1;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue