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)
|
||||
{
|
||||
string error = "";
|
||||
int statusCode = 0;
|
||||
if (ex.Response != null)
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(ex.Response.GetResponseStream()))
|
||||
{
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
Un.java
12
Un.java
|
|
@ -555,6 +555,18 @@ public class Un {
|
|||
int status = conn.getResponseCode();
|
||||
if (status < 200 || status >= 300) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
30
un.clj
30
un.clj
|
|
@ -127,6 +127,22 @@
|
|||
(let [message (str timestamp ":" method ":" path ":" body)]
|
||||
(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]
|
||||
(if secret-key
|
||||
(let [timestamp (str (quot (System/currentTimeMillis) 1000))
|
||||
|
|
@ -148,6 +164,7 @@
|
|||
["-d" (str "@" tmp-file)])
|
||||
{:keys [out]} (apply sh args)]
|
||||
(io/delete-file tmp-file true)
|
||||
(check-clock-drift-error out)
|
||||
out)))
|
||||
|
||||
(defn curl-get [api-key endpoint]
|
||||
|
|
@ -155,16 +172,20 @@
|
|||
auth-headers (build-auth-headers public-key secret-key "GET" endpoint "")
|
||||
args (concat ["curl" "-s"
|
||||
(str "https://api.unsandbox.com" endpoint)]
|
||||
auth-headers)]
|
||||
(:out (apply sh args))))
|
||||
auth-headers)
|
||||
result (:out (apply sh args))]
|
||||
(check-clock-drift-error result)
|
||||
result))
|
||||
|
||||
(defn curl-delete [api-key endpoint]
|
||||
(let [[public-key secret-key] (get-api-keys)
|
||||
auth-headers (build-auth-headers public-key secret-key "DELETE" endpoint "")
|
||||
args (concat ["curl" "-s" "-X" "DELETE"
|
||||
(str "https://api.unsandbox.com" endpoint)]
|
||||
auth-headers)]
|
||||
(:out (apply sh args))))
|
||||
auth-headers)
|
||||
result (:out (apply sh args))]
|
||||
(check-clock-drift-error result)
|
||||
result))
|
||||
|
||||
(defn curl-portal-post [api-key endpoint json-data]
|
||||
(let [tmp-file (str "/tmp/un_clj_portal_" (rand-int 999999) ".json")
|
||||
|
|
@ -178,6 +199,7 @@
|
|||
["-d" (str "@" tmp-file)])
|
||||
{:keys [out]} (apply sh args)]
|
||||
(io/delete-file tmp-file true)
|
||||
(check-clock-drift-error out)
|
||||
out)))
|
||||
|
||||
(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 '"
|
||||
FUNCTION TRIM(WS-SECRET-KEY)
|
||||
"' | 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 'Authorization: Bearer "
|
||||
FUNCTION TRIM(WS-PUBLIC-KEY)
|
||||
"' "
|
||||
"-H 'X-Timestamp: '$TS "
|
||||
"-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 | "
|
||||
"sed 's/^/\x1b[34m/' | sed 's/$/\x1b[0m/'; "
|
||||
"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,
|
||||
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) ->
|
||||
if
|
||||
SecretKey =/= false ->
|
||||
|
|
@ -404,7 +423,9 @@ curl_post(ApiKey, Endpoint, TmpFile) ->
|
|||
" -H 'Content-Type: application/json'" ++
|
||||
AuthHeaders ++
|
||||
" -d @" ++ TmpFile,
|
||||
os:cmd(Cmd).
|
||||
Result = os:cmd(Cmd),
|
||||
check_clock_drift_error(Result),
|
||||
Result.
|
||||
|
||||
curl_post_portal(ApiKey, Endpoint, Data) ->
|
||||
TmpFile = write_temp_file(Data),
|
||||
|
|
@ -416,6 +437,7 @@ curl_post_portal(ApiKey, Endpoint, Data) ->
|
|||
" -d @" ++ TmpFile,
|
||||
Result = os:cmd(Cmd),
|
||||
file:delete(TmpFile),
|
||||
check_clock_drift_error(Result),
|
||||
Result.
|
||||
|
||||
curl_get(ApiKey, Endpoint) ->
|
||||
|
|
@ -423,14 +445,18 @@ curl_get(ApiKey, Endpoint) ->
|
|||
AuthHeaders = build_auth_headers(PublicKey, SecretKey, "GET", Endpoint, ""),
|
||||
Cmd = "curl -s https://api.unsandbox.com" ++ Endpoint ++
|
||||
AuthHeaders,
|
||||
os:cmd(Cmd).
|
||||
Result = os:cmd(Cmd),
|
||||
check_clock_drift_error(Result),
|
||||
Result.
|
||||
|
||||
curl_delete(ApiKey, Endpoint) ->
|
||||
{PublicKey, SecretKey} = get_api_keys(),
|
||||
AuthHeaders = build_auth_headers(PublicKey, SecretKey, "DELETE", Endpoint, ""),
|
||||
Cmd = "curl -s -X DELETE https://api.unsandbox.com" ++ Endpoint ++
|
||||
AuthHeaders,
|
||||
os:cmd(Cmd).
|
||||
Result = os:cmd(Cmd),
|
||||
check_clock_drift_error(Result),
|
||||
Result.
|
||||
|
||||
%% Argument parsing
|
||||
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)
|
||||
|
||||
File.rm(tmp_file)
|
||||
check_clock_drift(output)
|
||||
output
|
||||
end
|
||||
|
||||
|
|
@ -497,6 +498,7 @@ defmodule Un do
|
|||
{output, _exit} = System.cmd("curl", args, stderr_to_stdout: true)
|
||||
|
||||
File.rm(tmp_file)
|
||||
check_clock_drift(output)
|
||||
output
|
||||
end
|
||||
|
||||
|
|
@ -511,6 +513,7 @@ defmodule Un do
|
|||
|
||||
{output, _exit} = System.cmd("curl", args, stderr_to_stdout: true)
|
||||
|
||||
check_clock_drift(output)
|
||||
output
|
||||
end
|
||||
|
||||
|
|
@ -525,6 +528,7 @@ defmodule Un do
|
|||
|
||||
{output, _exit} = System.cmd("curl", args, stderr_to_stdout: true)
|
||||
|
||||
check_clock_drift(output)
|
||||
output
|
||||
end
|
||||
|
||||
|
|
@ -554,6 +558,26 @@ defmodule Un do
|
|||
defp get_opt([_arg | rest], long, short, default) do
|
||||
get_opt(rest, long, short, default)
|
||||
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
|
||||
|
||||
Un.main(System.argv())
|
||||
|
|
|
|||
10
un.f90
10
un.f90
|
|
@ -167,6 +167,16 @@ contains
|
|||
'-H "X-Timestamp: $TS" ', &
|
||||
'-H "X-Signature: $SIG" ', &
|
||||
'--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 | ', &
|
||||
'sed "s/^/\x1b[34m/" | sed "s/$/\x1b[0m/"; ', &
|
||||
'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" 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" 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
|
||||
|
||||
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()
|
||||
else
|
||||
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
|
||||
|
||||
let cmdExecute (args: Args) =
|
||||
|
|
@ -432,6 +443,17 @@ let cmdKey (args: Args) =
|
|||
with _ -> body
|
||||
else
|
||||
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
|
||||
exit 1
|
||||
|
||||
|
|
|
|||
36
un.hs
36
un.hs
|
|
@ -383,6 +383,28 @@ serviceCommand opts = do
|
|||
putStrLn $ green ++ "Service created" ++ reset
|
||||
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
|
||||
curlPost :: String -> String -> String -> IO (ExitCode, String, String)
|
||||
curlPost apiKey url body = do
|
||||
|
|
@ -395,6 +417,8 @@ curlPost apiKey url body = do
|
|||
, url
|
||||
, "-H", "Content-Type: application/json"
|
||||
] ++ authHeaders ++ ["-d", body]) ""
|
||||
-- Check for clock drift error
|
||||
checkClockDriftError stdout
|
||||
return (exitCode, stdout, stderr)
|
||||
|
||||
curlGet :: String -> String -> IO (ExitCode, String, String)
|
||||
|
|
@ -402,16 +426,22 @@ curlGet apiKey url = do
|
|||
(publicKey, secretKey) <- getApiKeys
|
||||
let path = drop (length "https://api.unsandbox.com") url
|
||||
authHeaders <- buildAuthHeaders publicKey secretKey "GET" path ""
|
||||
readProcessWithExitCode "curl"
|
||||
(exitCode, stdout, stderr) <- readProcessWithExitCode "curl"
|
||||
([ "-s", url ] ++ authHeaders) ""
|
||||
-- Check for clock drift error
|
||||
checkClockDriftError stdout
|
||||
return (exitCode, stdout, stderr)
|
||||
|
||||
curlDelete :: String -> String -> IO (ExitCode, String, String)
|
||||
curlDelete apiKey url = do
|
||||
(publicKey, secretKey) <- getApiKeys
|
||||
let path = drop (length "https://api.unsandbox.com") url
|
||||
authHeaders <- buildAuthHeaders publicKey secretKey "DELETE" path ""
|
||||
readProcessWithExitCode "curl"
|
||||
(exitCode, stdout, stderr) <- readProcessWithExitCode "curl"
|
||||
([ "-s", "-X", "DELETE", url ] ++ authHeaders) ""
|
||||
-- Check for clock drift error
|
||||
checkClockDriftError stdout
|
||||
return (exitCode, stdout, stderr)
|
||||
|
||||
-- Get API keys from environment
|
||||
getApiKeys :: IO (String, Maybe String)
|
||||
|
|
@ -615,4 +645,6 @@ curlPostPortal apiKey url body = do
|
|||
, url
|
||||
, "-H", "Content-Type: application/json"
|
||||
] ++ authHeaders ++ ["-d", body]) ""
|
||||
-- Check for clock drift error
|
||||
checkClockDriftError stdout
|
||||
return (exitCode, stdout, stderr)
|
||||
|
|
|
|||
46
un.lisp
46
un.lisp
|
|
@ -101,40 +101,62 @@
|
|||
while line do (format out "~a~%" line))
|
||||
(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)
|
||||
(let ((tmp-file (write-temp-file json-data)))
|
||||
(unwind-protect
|
||||
(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"
|
||||
(format nil "https://api.unsandbox.com~a" endpoint)
|
||||
"-H" "Content-Type: application/json")))
|
||||
(run-curl (append base-args auth-headers (list "-d" (format nil "@~a" tmp-file))))))
|
||||
"-H" "Content-Type: application/json"))
|
||||
(response (run-curl (append base-args auth-headers (list "-d" (format nil "@~a" tmp-file))))))
|
||||
(check-clock-drift response)
|
||||
response))
|
||||
(delete-file tmp-file))))
|
||||
|
||||
(defun curl-get (api-key endpoint)
|
||||
(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"
|
||||
(format nil "https://api.unsandbox.com~a" endpoint))))
|
||||
(run-curl (append base-args auth-headers)))))
|
||||
(format nil "https://api.unsandbox.com~a" endpoint)))
|
||||
(response (run-curl (append base-args auth-headers))))
|
||||
(check-clock-drift response)
|
||||
response)))
|
||||
|
||||
(defun curl-delete (api-key endpoint)
|
||||
(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"
|
||||
(format nil "https://api.unsandbox.com~a" endpoint))))
|
||||
(run-curl (append base-args auth-headers)))))
|
||||
(format nil "https://api.unsandbox.com~a" endpoint)))
|
||||
(response (run-curl (append base-args auth-headers))))
|
||||
(check-clock-drift response)
|
||||
response)))
|
||||
|
||||
(defun curl-post-portal (api-key endpoint json-data)
|
||||
(let ((tmp-file (write-temp-file json-data)))
|
||||
(unwind-protect
|
||||
(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"
|
||||
(format nil "~a~a" *portal-base* endpoint)
|
||||
"-H" "Content-Type: application/json")))
|
||||
(run-curl (append base-args auth-headers (list "-d" (format nil "@~a" tmp-file))))))
|
||||
"-H" "Content-Type: application/json"))
|
||||
(response (run-curl (append base-args auth-headers (list "-d" (format nil "@~a" tmp-file))))))
|
||||
(check-clock-drift response)
|
||||
response))
|
||||
(delete-file tmp-file))))
|
||||
|
||||
(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) {
|
||||
const char* cKey = [key UTF8String];
|
||||
const char* cMessage = [message UTF8String];
|
||||
|
|
@ -170,6 +188,7 @@ NSDictionary* apiRequest(NSString* endpoint, NSString* method, NSDictionary* dat
|
|||
if (responseData) {
|
||||
NSString* errMsg = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
|
||||
fprintf(stderr, "%s\n", [errMsg UTF8String]);
|
||||
checkClockDrift(errMsg);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
|
@ -350,6 +369,7 @@ NSDictionary* portalRequest(NSString* endpoint, NSString* method, NSDictionary*
|
|||
if (responseData) {
|
||||
NSString* errMsg = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
|
||||
fprintf(stderr, "%s\n", [errMsg UTF8String]);
|
||||
checkClockDrift(errMsg);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
|
|
|||
29
un.ml
29
un.ml
|
|
@ -112,6 +112,31 @@ let escape_json s =
|
|||
) s;
|
||||
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 *)
|
||||
let curl_post api_key endpoint json =
|
||||
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 _ = Unix.close_process_in ic in
|
||||
Sys.remove tmp_file;
|
||||
check_clock_drift output;
|
||||
output
|
||||
|
||||
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 _ = Unix.close_process_in ic in
|
||||
Sys.remove tmp_file;
|
||||
check_clock_drift output;
|
||||
output
|
||||
|
||||
let curl_get api_key endpoint =
|
||||
|
|
@ -165,6 +192,7 @@ let curl_get api_key endpoint =
|
|||
in
|
||||
let output = read_all "" in
|
||||
let _ = Unix.close_process_in ic in
|
||||
check_clock_drift output;
|
||||
output
|
||||
|
||||
let curl_delete api_key endpoint =
|
||||
|
|
@ -181,6 +209,7 @@ let curl_delete api_key endpoint =
|
|||
in
|
||||
let output = read_all "" in
|
||||
let _ = Unix.close_process_in ic in
|
||||
check_clock_drift output;
|
||||
output
|
||||
|
||||
(* 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
|
||||
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]),
|
||||
shell(Cmd, 0).
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ session_list :-
|
|||
get_public_key(PublicKey),
|
||||
get_secret_key(SecretKey),
|
||||
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]),
|
||||
shell(Cmd, 0).
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ service_list :-
|
|||
get_public_key(PublicKey),
|
||||
get_secret_key(SecretKey),
|
||||
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]),
|
||||
shell(Cmd, 0).
|
||||
|
||||
|
|
@ -252,11 +252,11 @@ validate_key(Extend) :-
|
|||
( Extend = true
|
||||
-> % Build command for --extend mode
|
||||
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])
|
||||
; % Build command for normal validation
|
||||
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])
|
||||
),
|
||||
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))
|
||||
}
|
||||
|
||||
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) {
|
||||
url <- paste0(API_BASE, endpoint)
|
||||
headers <- add_headers(
|
||||
|
|
@ -129,7 +148,9 @@ api_request <- function(endpoint, public_key, secret_key, method = "GET", data =
|
|||
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)
|
||||
}, error = function(e) {
|
||||
cat(sprintf("%sError: Request failed: %s%s\n", RED, e$message, RESET), file = stderr())
|
||||
|
|
@ -292,7 +313,9 @@ cmd_key <- function(args) {
|
|||
|
||||
tryCatch({
|
||||
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)) {
|
||||
extend_url <- paste0(PORTAL_BASE, "/keys/extend?pk=", result$public_key)
|
||||
|
|
@ -331,7 +354,9 @@ cmd_key <- function(args) {
|
|||
|
||||
tryCatch({
|
||||
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"
|
||||
|
||||
|
|
|
|||
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;
|
||||
}
|
||||
|
||||
# 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
48
un.scm
48
un.scm
|
|
@ -117,6 +117,18 @@
|
|||
(loop (cons line lines)))))))
|
||||
(close-pipe port)
|
||||
(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))
|
||||
|
||||
(define (curl-get api-key endpoint)
|
||||
|
|
@ -133,6 +145,18 @@
|
|||
(string-join (reverse lines) "\n")
|
||||
(loop (cons line lines)))))))
|
||||
(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))
|
||||
|
||||
(define (curl-delete api-key endpoint)
|
||||
|
|
@ -149,6 +173,18 @@
|
|||
(string-join (reverse lines) "\n")
|
||||
(loop (cons line lines)))))))
|
||||
(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))
|
||||
|
||||
(define (curl-post-portal api-key endpoint json-data)
|
||||
|
|
@ -169,6 +205,18 @@
|
|||
(loop (cons line lines)))))))
|
||||
(close-pipe port)
|
||||
(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))
|
||||
|
||||
(define (get-api-keys)
|
||||
|
|
|
|||
68
un.zig
68
un.zig
|
|
@ -362,6 +362,21 @@ pub fn main() !u8 {
|
|||
defer allocator.free(json_content);
|
||||
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")
|
||||
const pk_prefix = "\"public_key\":\"";
|
||||
var public_key_value: ?[]const u8 = null;
|
||||
|
|
@ -401,6 +416,21 @@ pub fn main() !u8 {
|
|||
defer allocator.free(json_content);
|
||||
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)
|
||||
const status_prefix = "\"status\":\"";
|
||||
var status: ?[]const u8 = null;
|
||||
|
|
@ -535,14 +565,44 @@ pub fn main() !u8 {
|
|||
// Execute with curl
|
||||
const auth_headers = try buildAuthCmd(allocator, "POST", "/execute", json_content, public_key, secret_key);
|
||||
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);
|
||||
|
||||
const result = std.c.system(cmd.ptr);
|
||||
std.debug.print("\n", .{});
|
||||
_ = std.c.system(cmd.ptr);
|
||||
|
||||
// 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
|
||||
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