Loading CHANGELOG.md +4 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,10 @@ All notable changes to this project are documented below. The format is based on [keep a changelog](http://keepachangelog.com) and this project uses [semantic versioning](http://semver.org). ## [Unreleased] ### Added - New Lua runtime functions to generate JWT tokens. - New Lua runtime functions to hash data using RSA SHA256. ### Changed - Log more information when authoritative match handlers receive too many data messages. - Ensure storage writes and deletes are performed in a consistent order within each batch. Loading data/modules/iap_verifier.lua 0 → 100644 +166 −0 Original line number Diff line number Diff line --[[ Copyright 2019 The Nakama Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --]] --[[ In-App Purchase Verification module. Using this module, you can check the validity of an Apple or Google IAP receipt. --]] local nk = require("nakama") local M = {} --[[ Sends a request to the Apple IAP Verification service. It will first try to validate against Production servers, and if code 21007 is returned, it will retry it with Sandbox servers. Request object match the following format: { receipt = "", -- base64 encoded receipt data received from client/iOS password = "", -- optional. Used to verify auto-renewable subscriptions. exclude_old_transactions = true -- optional. Return only the most recent transaction for auto-renewable subscriptions. } This function will return a Lua table that represents the data in this page: https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html This function can also raise an error in case of bad network, or invalid receipt data. --]] function M.verify_payment_apple(request) local url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt" local url_production = "https://buy.itunes.apple.com/verifyReceipt" local http_body = nk.json_encode({ ["receipt-data"] = request.receipt, ["password"] = request.password, ["exclude-old-transactions"] = request.exclude_old_transactions }) local http_headers = { ["Content-Type"] = "application/json", ["Accept"] = "application/json" } local success, code, _, body = pcall(nk.http_request, url_production, "POST", http_headers, http_body) if (not success) then nk.logger_warn(("Network error occurred: %q"):format(code)) error(code) elseif (code == 200) then return nk.json_decode(body) elseif (code == 400) then local response = nk.json_decode(body) if (response.status == 21007) then -- was supposed to be sent to sandbox local success, code, _, body = pcall(nk.http_request, url_sandbox, "POST", http_headers, http_body) if (not success) then nk.logger_warn(("Network error occurred: %q"):format(code)) error(code) elseif (code == 200) then return nk.json_decode(body) end end end error(body) end function M.google_obtain_access_token(client_email, private_key) local auth_url = "https://accounts.google.com/o/oauth2/auth" local scope = "https://www.googleapis.com/auth/androidpublisher" local exp = nk.time() + 3600000 -- current time + 1hr added in ms local iat = nk.time() local algo_type = "RS256" local jwt_claimset = nk.base64url_encode({ ["iss"] = client_email, ["scope"] = scope, ["aud"] = auth_url, ["exp"] = exp, ["iat"] = iat }) local jwt_token = nk.jwt_generate(algo_type, private_key, jwt_claimset) local grant_type = "urn%3ietf%3params%3oauth%3grant-type%3jwt-bearer" local form_data = "grant_type=" .. grant_type .. "&assertion=" .. jwt_token local http_headers = {["Content-Type"] = "application/x-www-form-urlencoded"} local success, code, _, body = pcall(nk.http_request, auth_url, "POST", http_headers, form_data) if (not success) then nk.logger_warn(("Network error occurred: %q"):format(code)) error(code) elseif (code == 200) then return nk.json_decode(body)["access_token"] end error(body) end --[[ Sends a request to the Google IAP Verification service. It will first try to obtain an access token using the service account provided. Request object match the following format: { is_subscription = false, -- set to true if it is subscription, otherwise product. product_id = "" -- Product ID, package_name = "" -- Product Name, receipt = "" -- Payment receipt in string format, client_email = "", -- Service account client email address. Retrieve this from Service account in JSON format. private_key = "", -- Service account private key. Retrieve this from Service account in JSON format. } For Products, this function will return a Lua table that represents the data in this page: https://developers.google.com/android-publisher/api-ref/purchases/products#resource For Subscritions, this function will return a Lua table that represents the data in this page: https://developers.google.com/android-publisher/api-ref/purchases/subscriptions This function can also raise an error in case of bad network, bad authentication or invalid receipt data. --]] function M.verify_payment_google(request) local success, access_token = pcall(M.google_obtain_access_token, request.client_email, request.private_key) if (not success) then nk.logger_warn(("Failed to obtain access token: %q"):format(access_token)) error(access_token) end local url_template = "https://www.googleapis.com/androidpublisher/v2/applications/%q/purchases/subscriptions/%q/tokens/%q?access_token=%q" if (not request.is_subscription) then url_template = "https://www.googleapis.com/androidpublisher/v2/applications/%q/purchases/products/%q/tokens/%q?access_token=%q" end local url = url_template:format(request.package_name, request.product_id, request.receipt, access_token) local http_headers = { ["Content-Type"] = "application/json", ["Accept"] = "application/json" } local success, code, _, body = pcall(nk.http_request, url, "GET", http_headers, nil) if (not success) then nk.logger_warn(("Network error occurred: %q"):format(code)) error(code) elseif (code == 200) then return nk.json_decode(body) end error(body) end return M data/modules/iap_verifier_rpc.lua 0 → 100644 +142 −0 Original line number Diff line number Diff line --[[ Copyright 2019 The Nakama Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --]] --[[ Client RPC calls to validate receipt with Apple or Google. --]] local nk = require("nakama") local iap = require("iap_verifier") --[[ This function expects the following information to come from Runtime environment variables: "password" -- Shares secret password obtained from Apple. Client must send through the following information: { receipt = "" -- base64 encoded receipt information } The response object will be: { "success": true "result": {} } or in case of an error: { "success": false "error": "" } This function will return result that represents the data in this page: https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html --]] local function apple_verify_payment(context, payload) -- In-App Purchase Shared Secret required to verify auto-renewable subscriptions. local password = context.env["iap_apple_password"] local json_payload = nk.json_decode(payload) local receipt = json_payload.receipt local success, result = pcall(iap.verify_payment_apple, { receipt = receipt, password = password, exclude_old_transactions = true }) if (not success) then nk.logger_warn(("Apple IAP verification failed - request: %q - response: %q"):format(payload, result)) return nk.json_encode({ ["success"] = false, ["error"] = result }) else nk.logger_info(("Apple IAP verification completed - request: %q - response: %q"):format(payload, result)) return nk.json_encode({ ["success"] = true, ["result"] = result }) end end nk.register_rpc(apple_verify_payment, "iap.apple_verify_payment") --[[ This function expects the following information to come from Runtime environment variables: "iap_google_service_account" -- Base64 encoded JSON file. Client must send through the following information: { product_id = "", package_name = "", receipt = "", is_subscription = false } The response object will be: { "success": true "result": {} } or in case of an error: { "success": false "error": "" } For Products, this function will return result that represents the data in this page: https://developers.google.com/android-publisher/api-ref/purchases/products#resource For Subscritions, this function will return result that represents the data in this page: https://developers.google.com/android-publisher/api-ref/purchases/subscriptions --]] local function google_verify_payment(context, payload) -- Google API Service Account JSON key file in base64. local service_account = nk.base64_decode(context.env["iap_google_service_account"]) local json_payload = nk.json_decode(payload) local product_id = json_payload.product_id local package_name = json_payload.package_name local receipt = json_payload.receipt local success, result = pcall(iap.verify_payment_google, { is_subscription = false, product_id = product_id, package_name = package_name, receipt = receipt, client_email = service_account["client_email"], private_key = service_account["private_key"], }) if (not success) then nk.logger_warn(("Google IAP verification failed - request: %q - response: %q"):format(payload, result)) return nk.json_encode({ ["success"] = false, ["error"] = result }) else nk.logger_info(("Google IAP verification completed - request: %q - response: %q"):format(payload, result)) return nk.json_encode({ ["success"] = true, ["result"] = result }) end end nk.register_rpc(google_verify_payment, "iap.google_verify_payment") server/runtime_lua_nakama.go +76 −0 Original line number Diff line number Diff line Loading @@ -16,12 +16,15 @@ package server import ( "bytes" "crypto" "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/md5" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "database/sql" "encoding/base64" "encoding/gob" Loading @@ -35,6 +38,7 @@ import ( "sync" "time" "github.com/dgrijalva/jwt-go" "github.com/golang/protobuf/jsonpb" "github.com/gofrs/uuid" Loading Loading @@ -125,6 +129,7 @@ func (n *RuntimeLuaNakamaModule) Loader(l *lua.LState) int { "uuid_bytes_to_string": n.uuidBytesToString, "uuid_string_to_bytes": n.uuidStringToBytes, "http_request": n.httpRequest, "jwt_generate": n.jwtGenerate, "json_encode": n.jsonEncode, "json_decode": n.jsonDecode, "base64_encode": n.base64Encode, Loading @@ -140,6 +145,7 @@ func (n *RuntimeLuaNakamaModule) Loader(l *lua.LState) int { "md5_hash": n.md5Hash, "sha256_hash": n.sha256Hash, "hmac_sha256_hash": n.hmacSHA256Hash, "rsa_sha256_hash": n.rsaSHA256Hash, "bcrypt_hash": n.bcryptHash, "bcrypt_compare": n.bcryptCompare, "authenticate_custom": n.authenticateCustom, Loading Loading @@ -699,6 +705,47 @@ func (n *RuntimeLuaNakamaModule) httpRequest(l *lua.LState) int { return 3 } func (n *RuntimeLuaNakamaModule) jwtGenerate(l *lua.LState) int { algoType := l.CheckString(1) if algoType == "" { l.ArgError(1, "expects string") return 0 } var signingMethod jwt.SigningMethod switch algoType { case "HS256": signingMethod = jwt.SigningMethodHS256 case "RS256": signingMethod = jwt.SigningMethodRS256 default: l.ArgError(3, "unsupported algo type - only allowed 'HS256', 'RS256'.") } signingKey := l.CheckString(2) if signingKey == "" { l.ArgError(2, "expects string") return 0 } claimsetTable := l.CheckTable(3) if claimsetTable == nil { l.ArgError(3, "expects nil") return 0 } claimset := RuntimeLuaConvertLuaValue(claimsetTable).(map[string]interface{}) jwtClaims := jwt.MapClaims{} for k, v := range claimset { jwtClaims[k] = v } token := jwt.NewWithClaims(signingMethod, jwtClaims) signedToken, _ := token.SignedString([]byte(signingKey)) l.Push(lua.LString(signedToken)) return 1 } func (n *RuntimeLuaNakamaModule) jsonEncode(l *lua.LState) int { value := l.Get(1) if value == nil { Loading Loading @@ -964,6 +1011,35 @@ func (n *RuntimeLuaNakamaModule) sha256Hash(l *lua.LState) int { return 1 } func (n *RuntimeLuaNakamaModule) rsaSHA256Hash(l *lua.LState) int { input := l.CheckString(1) if input == "" { l.ArgError(1, "expects input string") return 0 } key := l.CheckString(2) if key == "" { l.ArgError(2, "expects key string") return 0 } rsaPrivateKey, err := x509.ParsePKCS1PrivateKey([]byte(key)) if err != nil { l.RaiseError("error parsing key: %v", err.Error()) return 0 } hashed := sha256.Sum256([]byte(input)) signature, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivateKey, crypto.SHA256, hashed[:]) if err != nil { l.RaiseError("error parsing key: %v", err.Error()) return 0 } l.Push(lua.LString(signature)) return 1 } func (n *RuntimeLuaNakamaModule) hmacSHA256Hash(l *lua.LState) int { input := l.CheckString(1) if input == "" { Loading Loading
CHANGELOG.md +4 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,10 @@ All notable changes to this project are documented below. The format is based on [keep a changelog](http://keepachangelog.com) and this project uses [semantic versioning](http://semver.org). ## [Unreleased] ### Added - New Lua runtime functions to generate JWT tokens. - New Lua runtime functions to hash data using RSA SHA256. ### Changed - Log more information when authoritative match handlers receive too many data messages. - Ensure storage writes and deletes are performed in a consistent order within each batch. Loading
data/modules/iap_verifier.lua 0 → 100644 +166 −0 Original line number Diff line number Diff line --[[ Copyright 2019 The Nakama Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --]] --[[ In-App Purchase Verification module. Using this module, you can check the validity of an Apple or Google IAP receipt. --]] local nk = require("nakama") local M = {} --[[ Sends a request to the Apple IAP Verification service. It will first try to validate against Production servers, and if code 21007 is returned, it will retry it with Sandbox servers. Request object match the following format: { receipt = "", -- base64 encoded receipt data received from client/iOS password = "", -- optional. Used to verify auto-renewable subscriptions. exclude_old_transactions = true -- optional. Return only the most recent transaction for auto-renewable subscriptions. } This function will return a Lua table that represents the data in this page: https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html This function can also raise an error in case of bad network, or invalid receipt data. --]] function M.verify_payment_apple(request) local url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt" local url_production = "https://buy.itunes.apple.com/verifyReceipt" local http_body = nk.json_encode({ ["receipt-data"] = request.receipt, ["password"] = request.password, ["exclude-old-transactions"] = request.exclude_old_transactions }) local http_headers = { ["Content-Type"] = "application/json", ["Accept"] = "application/json" } local success, code, _, body = pcall(nk.http_request, url_production, "POST", http_headers, http_body) if (not success) then nk.logger_warn(("Network error occurred: %q"):format(code)) error(code) elseif (code == 200) then return nk.json_decode(body) elseif (code == 400) then local response = nk.json_decode(body) if (response.status == 21007) then -- was supposed to be sent to sandbox local success, code, _, body = pcall(nk.http_request, url_sandbox, "POST", http_headers, http_body) if (not success) then nk.logger_warn(("Network error occurred: %q"):format(code)) error(code) elseif (code == 200) then return nk.json_decode(body) end end end error(body) end function M.google_obtain_access_token(client_email, private_key) local auth_url = "https://accounts.google.com/o/oauth2/auth" local scope = "https://www.googleapis.com/auth/androidpublisher" local exp = nk.time() + 3600000 -- current time + 1hr added in ms local iat = nk.time() local algo_type = "RS256" local jwt_claimset = nk.base64url_encode({ ["iss"] = client_email, ["scope"] = scope, ["aud"] = auth_url, ["exp"] = exp, ["iat"] = iat }) local jwt_token = nk.jwt_generate(algo_type, private_key, jwt_claimset) local grant_type = "urn%3ietf%3params%3oauth%3grant-type%3jwt-bearer" local form_data = "grant_type=" .. grant_type .. "&assertion=" .. jwt_token local http_headers = {["Content-Type"] = "application/x-www-form-urlencoded"} local success, code, _, body = pcall(nk.http_request, auth_url, "POST", http_headers, form_data) if (not success) then nk.logger_warn(("Network error occurred: %q"):format(code)) error(code) elseif (code == 200) then return nk.json_decode(body)["access_token"] end error(body) end --[[ Sends a request to the Google IAP Verification service. It will first try to obtain an access token using the service account provided. Request object match the following format: { is_subscription = false, -- set to true if it is subscription, otherwise product. product_id = "" -- Product ID, package_name = "" -- Product Name, receipt = "" -- Payment receipt in string format, client_email = "", -- Service account client email address. Retrieve this from Service account in JSON format. private_key = "", -- Service account private key. Retrieve this from Service account in JSON format. } For Products, this function will return a Lua table that represents the data in this page: https://developers.google.com/android-publisher/api-ref/purchases/products#resource For Subscritions, this function will return a Lua table that represents the data in this page: https://developers.google.com/android-publisher/api-ref/purchases/subscriptions This function can also raise an error in case of bad network, bad authentication or invalid receipt data. --]] function M.verify_payment_google(request) local success, access_token = pcall(M.google_obtain_access_token, request.client_email, request.private_key) if (not success) then nk.logger_warn(("Failed to obtain access token: %q"):format(access_token)) error(access_token) end local url_template = "https://www.googleapis.com/androidpublisher/v2/applications/%q/purchases/subscriptions/%q/tokens/%q?access_token=%q" if (not request.is_subscription) then url_template = "https://www.googleapis.com/androidpublisher/v2/applications/%q/purchases/products/%q/tokens/%q?access_token=%q" end local url = url_template:format(request.package_name, request.product_id, request.receipt, access_token) local http_headers = { ["Content-Type"] = "application/json", ["Accept"] = "application/json" } local success, code, _, body = pcall(nk.http_request, url, "GET", http_headers, nil) if (not success) then nk.logger_warn(("Network error occurred: %q"):format(code)) error(code) elseif (code == 200) then return nk.json_decode(body) end error(body) end return M
data/modules/iap_verifier_rpc.lua 0 → 100644 +142 −0 Original line number Diff line number Diff line --[[ Copyright 2019 The Nakama Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --]] --[[ Client RPC calls to validate receipt with Apple or Google. --]] local nk = require("nakama") local iap = require("iap_verifier") --[[ This function expects the following information to come from Runtime environment variables: "password" -- Shares secret password obtained from Apple. Client must send through the following information: { receipt = "" -- base64 encoded receipt information } The response object will be: { "success": true "result": {} } or in case of an error: { "success": false "error": "" } This function will return result that represents the data in this page: https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html --]] local function apple_verify_payment(context, payload) -- In-App Purchase Shared Secret required to verify auto-renewable subscriptions. local password = context.env["iap_apple_password"] local json_payload = nk.json_decode(payload) local receipt = json_payload.receipt local success, result = pcall(iap.verify_payment_apple, { receipt = receipt, password = password, exclude_old_transactions = true }) if (not success) then nk.logger_warn(("Apple IAP verification failed - request: %q - response: %q"):format(payload, result)) return nk.json_encode({ ["success"] = false, ["error"] = result }) else nk.logger_info(("Apple IAP verification completed - request: %q - response: %q"):format(payload, result)) return nk.json_encode({ ["success"] = true, ["result"] = result }) end end nk.register_rpc(apple_verify_payment, "iap.apple_verify_payment") --[[ This function expects the following information to come from Runtime environment variables: "iap_google_service_account" -- Base64 encoded JSON file. Client must send through the following information: { product_id = "", package_name = "", receipt = "", is_subscription = false } The response object will be: { "success": true "result": {} } or in case of an error: { "success": false "error": "" } For Products, this function will return result that represents the data in this page: https://developers.google.com/android-publisher/api-ref/purchases/products#resource For Subscritions, this function will return result that represents the data in this page: https://developers.google.com/android-publisher/api-ref/purchases/subscriptions --]] local function google_verify_payment(context, payload) -- Google API Service Account JSON key file in base64. local service_account = nk.base64_decode(context.env["iap_google_service_account"]) local json_payload = nk.json_decode(payload) local product_id = json_payload.product_id local package_name = json_payload.package_name local receipt = json_payload.receipt local success, result = pcall(iap.verify_payment_google, { is_subscription = false, product_id = product_id, package_name = package_name, receipt = receipt, client_email = service_account["client_email"], private_key = service_account["private_key"], }) if (not success) then nk.logger_warn(("Google IAP verification failed - request: %q - response: %q"):format(payload, result)) return nk.json_encode({ ["success"] = false, ["error"] = result }) else nk.logger_info(("Google IAP verification completed - request: %q - response: %q"):format(payload, result)) return nk.json_encode({ ["success"] = true, ["result"] = result }) end end nk.register_rpc(google_verify_payment, "iap.google_verify_payment")
server/runtime_lua_nakama.go +76 −0 Original line number Diff line number Diff line Loading @@ -16,12 +16,15 @@ package server import ( "bytes" "crypto" "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/md5" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "database/sql" "encoding/base64" "encoding/gob" Loading @@ -35,6 +38,7 @@ import ( "sync" "time" "github.com/dgrijalva/jwt-go" "github.com/golang/protobuf/jsonpb" "github.com/gofrs/uuid" Loading Loading @@ -125,6 +129,7 @@ func (n *RuntimeLuaNakamaModule) Loader(l *lua.LState) int { "uuid_bytes_to_string": n.uuidBytesToString, "uuid_string_to_bytes": n.uuidStringToBytes, "http_request": n.httpRequest, "jwt_generate": n.jwtGenerate, "json_encode": n.jsonEncode, "json_decode": n.jsonDecode, "base64_encode": n.base64Encode, Loading @@ -140,6 +145,7 @@ func (n *RuntimeLuaNakamaModule) Loader(l *lua.LState) int { "md5_hash": n.md5Hash, "sha256_hash": n.sha256Hash, "hmac_sha256_hash": n.hmacSHA256Hash, "rsa_sha256_hash": n.rsaSHA256Hash, "bcrypt_hash": n.bcryptHash, "bcrypt_compare": n.bcryptCompare, "authenticate_custom": n.authenticateCustom, Loading Loading @@ -699,6 +705,47 @@ func (n *RuntimeLuaNakamaModule) httpRequest(l *lua.LState) int { return 3 } func (n *RuntimeLuaNakamaModule) jwtGenerate(l *lua.LState) int { algoType := l.CheckString(1) if algoType == "" { l.ArgError(1, "expects string") return 0 } var signingMethod jwt.SigningMethod switch algoType { case "HS256": signingMethod = jwt.SigningMethodHS256 case "RS256": signingMethod = jwt.SigningMethodRS256 default: l.ArgError(3, "unsupported algo type - only allowed 'HS256', 'RS256'.") } signingKey := l.CheckString(2) if signingKey == "" { l.ArgError(2, "expects string") return 0 } claimsetTable := l.CheckTable(3) if claimsetTable == nil { l.ArgError(3, "expects nil") return 0 } claimset := RuntimeLuaConvertLuaValue(claimsetTable).(map[string]interface{}) jwtClaims := jwt.MapClaims{} for k, v := range claimset { jwtClaims[k] = v } token := jwt.NewWithClaims(signingMethod, jwtClaims) signedToken, _ := token.SignedString([]byte(signingKey)) l.Push(lua.LString(signedToken)) return 1 } func (n *RuntimeLuaNakamaModule) jsonEncode(l *lua.LState) int { value := l.Get(1) if value == nil { Loading Loading @@ -964,6 +1011,35 @@ func (n *RuntimeLuaNakamaModule) sha256Hash(l *lua.LState) int { return 1 } func (n *RuntimeLuaNakamaModule) rsaSHA256Hash(l *lua.LState) int { input := l.CheckString(1) if input == "" { l.ArgError(1, "expects input string") return 0 } key := l.CheckString(2) if key == "" { l.ArgError(2, "expects key string") return 0 } rsaPrivateKey, err := x509.ParsePKCS1PrivateKey([]byte(key)) if err != nil { l.RaiseError("error parsing key: %v", err.Error()) return 0 } hashed := sha256.Sum256([]byte(input)) signature, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivateKey, crypto.SHA256, hashed[:]) if err != nil { l.RaiseError("error parsing key: %v", err.Error()) return 0 } l.Push(lua.LString(signature)) return 1 } func (n *RuntimeLuaNakamaModule) hmacSHA256Hash(l *lua.LState) int { input := l.CheckString(1) if input == "" { Loading