You've already forked utilities
feat: add initial implementation of Go utilities and CI workflows
This commit is contained in:
48
.gitea/workflows/publish.yml
Normal file
48
.gitea/workflows/publish.yml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
name: 🚀 Publish Release Package
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
env:
|
||||||
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 🛡️ 🔒 Add Siteworx CA Certificates
|
||||||
|
run: |
|
||||||
|
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
|
||||||
|
update-ca-certificates
|
||||||
|
|
||||||
|
- name: 📥 Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install gopack
|
||||||
|
run: |
|
||||||
|
curl -L https://github.com/cloudsmith-io/gopack/releases/download/v0.6.0/gopack_0.6.0_linux_amd64.tar.gz -o /tmp/gopack.tar.gz
|
||||||
|
tar -xzf /tmp/gopack.tar.gz -C /tmp
|
||||||
|
mv /tmp/gopack /usr/local/bin/gopack
|
||||||
|
|
||||||
|
- name: 📦 Build Go Package
|
||||||
|
run: |
|
||||||
|
gopack ${{ github.ref_name }} .
|
||||||
|
|
||||||
|
- name: 📦 Publish Build Artifacts
|
||||||
|
uses: christopherhx/gitea-upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.ref_name }}
|
||||||
|
path: ${{ github.ref_name }}.zip
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
- name: ☁️ Upload release package
|
||||||
|
run: |
|
||||||
|
curl --user ${{ secrets.PACKAGE_PUBLISH_USER }}:${{ secrets.PACKAGE_PUBLISH_TOKEN }} \
|
||||||
|
--upload-file ${{ github.ref_name }}.zip \
|
||||||
|
${{ gitea.server_url }}/api/packages/packages/go/upload
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error uploading release package"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Upload successful"
|
||||||
77
.gitea/workflows/tests.yml
Normal file
77
.gitea/workflows/tests.yml
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
on:
|
||||||
|
workflow_dispatch: {}
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
name: 🧪 ✨ Tests Workflow
|
||||||
|
|
||||||
|
env:
|
||||||
|
GO_VERSION: '1.24.3'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-go:
|
||||||
|
name: 🔍 🐹 Go Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: 🛡️ 🔒 Add Siteworx CA Certificates
|
||||||
|
run: |
|
||||||
|
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
|
||||||
|
update-ca-certificates
|
||||||
|
|
||||||
|
- name: ⚙️ 🐹 Set up Go Environment
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: 📖 🔍 Checkout Repository Code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: 📦 📥 Install Dependencies
|
||||||
|
run: |
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
- name: ✅ 🔍 Run Go Lint
|
||||||
|
uses: golangci/golangci-lint-action@v8.0.0
|
||||||
|
|
||||||
|
test-go:
|
||||||
|
name: 🔍 🐹 Go Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: 🛡️ 🔒 Add Siteworx CA Certificates
|
||||||
|
run: |
|
||||||
|
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
|
||||||
|
update-ca-certificates
|
||||||
|
|
||||||
|
- name: ⚙️ 🐹 Set up Go Environment
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: 📖 🔍 Checkout Repository Code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: 📦 📥 Install Dependencies
|
||||||
|
run: |
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
- name: ✅ 🔍 Run Go Tests
|
||||||
|
run: |
|
||||||
|
go test -v ./... -coverprofile=coverage.out
|
||||||
|
|
||||||
|
- name: 📊 📈 Upload Coverage Report
|
||||||
|
env:
|
||||||
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
|
uses: christopherhx/gitea-upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: coverage-report
|
||||||
|
path: coverage.out
|
||||||
|
retention-days: 7
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.idea/
|
||||||
26
.golangci.yml
Normal file
26
.golangci.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
version: "2"
|
||||||
|
linters:
|
||||||
|
default: standard
|
||||||
|
enable:
|
||||||
|
- whitespace
|
||||||
|
- tagalign
|
||||||
|
- reassign
|
||||||
|
- bodyclose
|
||||||
|
- contextcheck
|
||||||
|
- containedctx
|
||||||
|
- godot
|
||||||
|
- usestdlibvars
|
||||||
|
- gochecknoglobals
|
||||||
|
- tagalign
|
||||||
|
- sqlclosecheck
|
||||||
|
- rowserrcheck
|
||||||
|
- recvcheck
|
||||||
|
- reassign
|
||||||
|
- predeclared
|
||||||
|
- maintidx
|
||||||
|
- mnd
|
||||||
|
|
||||||
|
formatters:
|
||||||
|
settings:
|
||||||
|
gofmt:
|
||||||
|
simplify: true
|
||||||
55
Env/env.go
Executable file
55
Env/env.go
Executable file
@@ -0,0 +1,55 @@
|
|||||||
|
package Env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EnvironmentVariable string
|
||||||
|
|
||||||
|
func (v EnvironmentVariable) GetName() string {
|
||||||
|
return string(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v EnvironmentVariable) GetEnvString(fallback string) string {
|
||||||
|
if value, ok := os.LookupEnv(string(v)); ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v EnvironmentVariable) GetEnvBool(fallback bool) bool {
|
||||||
|
value, ok := os.LookupEnv(string(v))
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
value = strings.ToLower(value)
|
||||||
|
trueValues := []string{"1", "true"}
|
||||||
|
|
||||||
|
for _, x := range trueValues {
|
||||||
|
if value == x {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v EnvironmentVariable) GetEnvInt(key string, fallback int64) int64 {
|
||||||
|
if value, ok := os.LookupEnv(key); ok {
|
||||||
|
i, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
69
Env/env_test.go
Normal file
69
Env/env_test.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package Env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEnvironmentVariable_GetName(t *testing.T) {
|
||||||
|
v := EnvironmentVariable("TEST_VAR")
|
||||||
|
if v.GetName() != "TEST_VAR" {
|
||||||
|
t.Errorf("expected 'TEST_VAR', got '%s'", v.GetName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvironmentVariable_GetEnvString(t *testing.T) {
|
||||||
|
const envName = "TEST_STRING"
|
||||||
|
_ = os.Setenv(envName, "value")
|
||||||
|
defer func() {
|
||||||
|
_ = os.Unsetenv(envName)
|
||||||
|
}()
|
||||||
|
|
||||||
|
v := EnvironmentVariable(envName)
|
||||||
|
got := v.GetEnvString("fallback")
|
||||||
|
if got != "value" {
|
||||||
|
t.Errorf("expected 'value', got '%s'", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = os.Unsetenv(envName)
|
||||||
|
got = v.GetEnvString("fallback")
|
||||||
|
if got != "fallback" {
|
||||||
|
t.Errorf("expected 'fallback', got '%s'", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvironmentVariable_GetEnvBool(t *testing.T) {
|
||||||
|
const envName = "TEST_BOOL"
|
||||||
|
v := EnvironmentVariable(envName)
|
||||||
|
|
||||||
|
_ = os.Setenv(envName, "true")
|
||||||
|
if !v.GetEnvBool(false) {
|
||||||
|
t.Errorf("expected true for 'true'")
|
||||||
|
}
|
||||||
|
_ = os.Setenv(envName, "1")
|
||||||
|
if !v.GetEnvBool(false) {
|
||||||
|
t.Errorf("expected true for '1'")
|
||||||
|
}
|
||||||
|
_ = os.Setenv(envName, "false")
|
||||||
|
if v.GetEnvBool(true) {
|
||||||
|
t.Errorf("expected false for 'false'")
|
||||||
|
}
|
||||||
|
_ = os.Unsetenv(envName)
|
||||||
|
if v.GetEnvBool(true) != true {
|
||||||
|
t.Errorf("expected fallback true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvironmentVariable_GetEnvInt(t *testing.T) {
|
||||||
|
const envName = "TEST_INT"
|
||||||
|
v := EnvironmentVariable(envName)
|
||||||
|
|
||||||
|
_ = os.Setenv(envName, "42")
|
||||||
|
if v.GetEnvInt(envName, 10) != 42 {
|
||||||
|
t.Errorf("expected 42")
|
||||||
|
}
|
||||||
|
_ = os.Unsetenv(envName)
|
||||||
|
if v.GetEnvInt(envName, 10) != 10 {
|
||||||
|
t.Errorf("expected fallback 10")
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Maps/maps.go
Executable file
11
Maps/maps.go
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
package Maps
|
||||||
|
|
||||||
|
//goland:noinspection GoUnusedExportedFunction // library function
|
||||||
|
func IsElementExist(s []string, str string) bool {
|
||||||
|
for _, v := range s {
|
||||||
|
if v == str {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
26
Maps/maps_test.go
Normal file
26
Maps/maps_test.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package Maps
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIsElementExist(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
s []string
|
||||||
|
str string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"element exists", []string{"a", "b", "c"}, "b", true},
|
||||||
|
{"element does not exist", []string{"a", "b", "c"}, "d", false},
|
||||||
|
{"empty slice", []string{}, "a", false},
|
||||||
|
{"multiple same elements", []string{"a", "b", "a"}, "a", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := IsElementExist(tt.s, tt.str)
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("IsElementExist(%v, %q) = %v; want %v", tt.s, tt.str, result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
114
Yubikey/otp.go
Executable file
114
Yubikey/otp.go
Executable file
@@ -0,0 +1,114 @@
|
|||||||
|
package Yubikey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
h string
|
||||||
|
t time.Time
|
||||||
|
otp string
|
||||||
|
presentedNonce string
|
||||||
|
nonce string
|
||||||
|
sl uint8
|
||||||
|
timestamp uint32
|
||||||
|
sessionCounter uint32
|
||||||
|
sessionUse uint32
|
||||||
|
status string
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyLen = 40
|
||||||
|
|
||||||
|
func (response *Response) Time() time.Time {
|
||||||
|
return response.t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response *Response) TimeStamp() uint32 {
|
||||||
|
return response.timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response *Response) Valid() bool {
|
||||||
|
return response.status == "OK" && response.nonce == response.presentedNonce
|
||||||
|
}
|
||||||
|
|
||||||
|
//goland:noinspection GoUnusedExportedFunction // library function
|
||||||
|
func GetVerification(otp string, clientId string) (*Response, error) {
|
||||||
|
nonce := randSeq(keyLen)
|
||||||
|
|
||||||
|
url := fmt.Sprintf(
|
||||||
|
"https://api.yubico.com/wsapi/2.0/verify?id=%s&otp=%s×tamp=1&nonce=%s",
|
||||||
|
clientId,
|
||||||
|
otp,
|
||||||
|
nonce,
|
||||||
|
)
|
||||||
|
|
||||||
|
response, _ := http.Get(url)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = response.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
body := make([]byte, response.ContentLength)
|
||||||
|
_, err := response.Body.Read(body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(string(body), "\r\n")
|
||||||
|
|
||||||
|
var status string
|
||||||
|
for k, part := range parts {
|
||||||
|
if strings.Contains(part, "status=") {
|
||||||
|
status = strings.Replace(parts[k], "status=", "", 1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if status != "OK" {
|
||||||
|
return nil, errors.New("invalid response status: " + status)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeString := strings.Replace(parts[1], "t=", "", 1)[0:20]
|
||||||
|
|
||||||
|
signedTime, err := time.Parse(time.RFC3339, timeString)
|
||||||
|
if err != nil {
|
||||||
|
println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sl, _ := strconv.ParseInt(strings.Replace(parts[4], "sl=", "", 1), 10, 8)
|
||||||
|
timestamp, _ := strconv.ParseInt(strings.Replace(parts[5], "timestamp=", "", 1), 10, 32)
|
||||||
|
sessionCounter, _ := strconv.ParseInt(strings.Replace(parts[6], "sessioncounter=", "", 1), 10, 32)
|
||||||
|
sessionUse, _ := strconv.ParseInt(strings.Replace(parts[7], "sessionuse=", "", 1), 10, 32)
|
||||||
|
|
||||||
|
keyResponse := Response{
|
||||||
|
h: strings.Replace(parts[0], "h=", "", 1),
|
||||||
|
t: signedTime,
|
||||||
|
otp: strings.Replace(parts[2], "otp=", "", 1),
|
||||||
|
presentedNonce: nonce,
|
||||||
|
nonce: strings.Replace(parts[3], "nonce=", "", 1),
|
||||||
|
sl: uint8(sl),
|
||||||
|
timestamp: uint32(timestamp),
|
||||||
|
sessionCounter: uint32(sessionCounter),
|
||||||
|
sessionUse: uint32(sessionUse),
|
||||||
|
status: status,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &keyResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func randSeq(n int) string {
|
||||||
|
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||||
|
|
||||||
|
b := make([]rune, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = letters[rand.Intn(len(letters))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
44
Yubikey/otp_test.go
Normal file
44
Yubikey/otp_test.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package Yubikey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test the Valid method of Response.
|
||||||
|
func TestResponse_Valid(t *testing.T) {
|
||||||
|
resp := &Response{
|
||||||
|
status: "OK",
|
||||||
|
nonce: "abc123",
|
||||||
|
presentedNonce: "abc123",
|
||||||
|
}
|
||||||
|
if !resp.Valid() {
|
||||||
|
t.Errorf("Expected Valid() to return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.status = "BAD"
|
||||||
|
if resp.Valid() {
|
||||||
|
t.Errorf("Expected Valid() to return false when status is not OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.status = "OK"
|
||||||
|
resp.nonce = "xyz"
|
||||||
|
if resp.Valid() {
|
||||||
|
t.Errorf("Expected Valid() to return false when nonce does not match presentedNonce")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the Time and TimeStamp methods.
|
||||||
|
func TestResponse_TimeAndTimeStamp(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
resp := &Response{
|
||||||
|
t: now,
|
||||||
|
timestamp: 12345,
|
||||||
|
}
|
||||||
|
if !resp.Time().Equal(now) {
|
||||||
|
t.Errorf("Expected Time() to return the correct time")
|
||||||
|
}
|
||||||
|
if resp.TimeStamp() != 12345 {
|
||||||
|
t.Errorf("Expected TimeStamp() to return 12345")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user