-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2c1497e
commit 70d45f0
Showing
7 changed files
with
478 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Perform linting of the code using golangci-lint | ||
name: golangci-lint | ||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
|
||
permissions: | ||
contents: read | ||
# Optional: allow read access to pull request. Use with `only-new-issues` option. | ||
# pull-requests: read | ||
|
||
jobs: | ||
golangci: | ||
name: lint | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-go@v5 | ||
with: | ||
go-version: stable | ||
- name: golangci-lint | ||
uses: golangci/golangci-lint-action@v6 | ||
with: | ||
version: latest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"strings" | ||
"time" | ||
|
||
"log/slog" | ||
|
||
"github.com/go-co-op/gocron/v2" | ||
"github.com/go-resty/resty/v2" | ||
"github.com/golang-jwt/jwt/v5" | ||
"github.com/hm-edu/harica-client/models" | ||
"github.com/pquerna/otp/totp" | ||
) | ||
|
||
const ( | ||
BaseURL = "https://cm.harica.gr" | ||
LoginPath = "/api/User/Login" | ||
LoginPathTotp = "/api/User/Login2FA" | ||
RefreshInterval = 15 * time.Minute | ||
) | ||
|
||
type Client struct { | ||
client *resty.Client | ||
scheduler gocron.Scheduler | ||
currentToken string | ||
} | ||
|
||
func NewClient(user, password, totpSeed string) (*Client, error) { | ||
c := Client{} | ||
err := c.prepareClient(user, password, totpSeed) | ||
if err != nil { | ||
return nil, err | ||
} | ||
s, err := gocron.NewScheduler() | ||
if err != nil { | ||
slog.Error("failed to create scheduler", slog.Any("error", err)) | ||
return nil, err | ||
} | ||
job, err := s.NewJob(gocron.DurationJob(RefreshInterval), gocron.NewTask(func() { | ||
c.prepareClient(user, password, totpSeed) | ||
})) | ||
if err != nil { | ||
slog.Error("failed to create job", slog.Any("error", err)) | ||
return nil, err | ||
} | ||
slog.Info("added job", slog.Any("job", job)) | ||
s.Start() | ||
c.scheduler = s | ||
return &c, nil | ||
} | ||
|
||
func (c *Client) prepareClient(user, password, totpSeed string) error { | ||
renew := false | ||
|
||
if c.currentToken != "" { | ||
// Check JWT | ||
token, _, err := jwt.NewParser().ParseUnverified(c.currentToken, jwt.MapClaims{}) | ||
if err != nil { | ||
return err | ||
} | ||
exp, err := token.Claims.GetExpirationTime() | ||
if err != nil { | ||
return err | ||
} | ||
if exp.Before(time.Now()) || exp.Before(time.Now().Add(RefreshInterval)) { | ||
renew = true | ||
} | ||
} | ||
if c.client == nil || c.currentToken == "" || renew { | ||
if totpSeed != "" { | ||
return c.loginTotp(user, password, totpSeed) | ||
} else { | ||
return c.login(user, password) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (c *Client) loginTotp(user, password, totpSeed string) error { | ||
r := resty.New() | ||
verificationToken, err := getVerificationToken(r) | ||
if err != nil { | ||
return err | ||
} | ||
otp, err := totp.GenerateCode(totpSeed, time.Now()) | ||
if err != nil { | ||
return err | ||
} | ||
resp, err := r. | ||
R().SetHeaderVerbatim("RequestVerificationToken", verificationToken). | ||
SetHeader("Content-Type", "application/json"). | ||
SetBody(map[string]string{"email": user, "password": password, "token": otp}). | ||
Post(BaseURL + LoginPathTotp) | ||
if err != nil { | ||
// handle error | ||
} | ||
tokenResp := strings.Trim(resp.String(), "\"") | ||
_, _, err = jwt.NewParser().ParseUnverified(tokenResp, jwt.MapClaims{}) | ||
if err != nil { | ||
return err | ||
} | ||
c.currentToken = tokenResp | ||
r = r.SetHeaders(map[string]string{"Authorization": c.currentToken}) | ||
r.SetPreRequestHook(func(c *resty.Client, r *http.Request) error { | ||
cli := resty.New().SetCookieJar(c.GetClient().Jar) | ||
verificationToken, err := getVerificationToken(cli) | ||
if err != nil { | ||
slog.Error("failed to get verification token", slog.Any("error", err)) | ||
return err | ||
} | ||
r.Header["RequestVerificationToken"] = []string{verificationToken} | ||
return nil | ||
}) | ||
c.client = r | ||
return nil | ||
} | ||
|
||
func (c *Client) login(user, password string) error { | ||
r := resty.New() | ||
verificationToken, err := getVerificationToken(r) | ||
if err != nil { | ||
return err | ||
} | ||
resp, err := r. | ||
R().SetHeaderVerbatim("RequestVerificationToken", verificationToken). | ||
SetHeader("Content-Type", "application/json"). | ||
SetBody(map[string]string{"email": user, "password": password}). | ||
Post(BaseURL + LoginPath) | ||
if err != nil { | ||
return err | ||
} | ||
c.currentToken = strings.Trim(resp.String(), "\"") | ||
r = r.SetHeaders(map[string]string{"Authorization": c.currentToken}) | ||
r.SetPreRequestHook(func(c *resty.Client, r *http.Request) error { | ||
cli := resty.New().SetCookieJar(c.GetClient().Jar) | ||
verificationToken, err := getVerificationToken(cli) | ||
if err != nil { | ||
slog.Error("failed to get verification token", slog.Any("error", err)) | ||
return err | ||
} | ||
r.Header.Add("RequestVerificationToken", verificationToken) | ||
return nil | ||
}) | ||
c.client = r | ||
return nil | ||
} | ||
|
||
func (c *Client) GetRevocationReasons() error { | ||
resp, err := c.client.R().Post(BaseURL + "/api/Certificate/GetRevocationReasons") | ||
if err != nil { | ||
return err | ||
} | ||
// handle response | ||
data := resp.String() | ||
fmt.Print(data) | ||
return nil | ||
} | ||
|
||
func (c *Client) GetDomainValidations() error { | ||
resp, err := c.client.R().Post(BaseURL + "/api/ServerCertificate/GetDomainValidations") | ||
if err != nil { | ||
return err | ||
} | ||
// handle response | ||
fmt.Print(resp.String()) | ||
return nil | ||
} | ||
|
||
type Domain struct { | ||
Domain string `json:"domain"` | ||
} | ||
|
||
func (c *Client) CheckMatchingOrganization(domains []string) ([]models.OrganizationResponse, error) { | ||
domainDto := make([]Domain, 0) | ||
for _, domain := range domains { | ||
domainDto = append(domainDto, Domain{Domain: domain}) | ||
} | ||
response := []models.OrganizationResponse{} | ||
_, err := c.client.R().SetHeader("Content-Type", "application/json").SetResult(&response).SetBody(domains).Post(BaseURL + "/api/ServerCertificate/CheckMachingOrganization") | ||
if err != nil { | ||
// handle error | ||
return nil, err | ||
} | ||
// handle response | ||
return response, nil | ||
} | ||
|
||
func (c *Client) CheckDomainNames(domains []string) ([]models.DomainResponse, error) { | ||
domainDto := make([]Domain, 0) | ||
for _, domain := range domains { | ||
domainDto = append(domainDto, Domain{Domain: domain}) | ||
} | ||
domainResp := make([]models.DomainResponse, 0) | ||
_, err := c.client.R().SetResult(&domainResp).SetHeader("Content-Type", "application/json").SetBody(domainDto).Post(BaseURL + "/api/ServerCertificate/CheckDomainNames") | ||
if err != nil { | ||
return nil, err | ||
} | ||
// handle response | ||
return domainResp, nil | ||
} | ||
|
||
func (c *Client) RequestCertificate() { | ||
|
||
} | ||
|
||
func (c *Client) RevokeCertificate() { | ||
|
||
} | ||
|
||
func (c *Client) GetPendingReviews() ([]models.ReviewResponse, error) { | ||
pending := []models.ReviewResponse{} | ||
_, err := c.client.R(). | ||
SetDebug(true). | ||
SetResult(&pending). | ||
SetHeader("Content-Type", "application/json"). | ||
SetBody(models.ReviewRequest{StartIndex: 0, Status: "Pending", FilterPostDTOs: []any{}}). | ||
Post(BaseURL + "/api/OrganizationValidatorSSL/GetSSLReviewableTransactions") | ||
if err != nil { | ||
return nil, err | ||
} | ||
return pending, nil | ||
} | ||
|
||
func (c *Client) ApproveRequest(id, message, value string) { | ||
|
||
c.client.R(). | ||
SetDebug(true). | ||
SetHeader("Content-Type", "multipart/form-data"). | ||
SetBody(map[string]string{"reviewId": id, "isValid": "true", "informApplicant": "true", "reviewMessage": message, "reviewValue": value}). | ||
Post(BaseURL + "/api/OrganizationValidatorSSL/UpdateReviews") | ||
|
||
} | ||
|
||
func (c *Client) Shutdown() { | ||
err := c.scheduler.Shutdown() | ||
if err != nil { | ||
// handle error | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
module github.com/hm-edu/harica-client | ||
|
||
go 1.23.3 | ||
|
||
require ( | ||
github.com/go-co-op/gocron/v2 v2.12.4 | ||
github.com/go-resty/resty/v2 v2.16.2 | ||
github.com/golang-jwt/jwt/v5 v5.2.1 | ||
github.com/pquerna/otp v1.4.0 | ||
golang.org/x/net v0.27.0 | ||
) | ||
|
||
require ( | ||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect | ||
github.com/google/uuid v1.6.0 // indirect | ||
github.com/jonboulle/clockwork v0.4.0 // indirect | ||
github.com/robfig/cron/v3 v3.0.1 // indirect | ||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= | ||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/go-co-op/gocron/v2 v2.12.4 h1:h1HWApo3T+61UrZqEY2qG1LUpDnB7tkYITxf6YIK354= | ||
github.com/go-co-op/gocron/v2 v2.12.4/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w= | ||
github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg= | ||
github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= | ||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= | ||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= | ||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= | ||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= | ||
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= | ||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= | ||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= | ||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | ||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | ||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= | ||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= | ||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= | ||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= | ||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= | ||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package main | ||
|
||
import ( | ||
"strings" | ||
|
||
"github.com/go-resty/resty/v2" | ||
"golang.org/x/net/html" | ||
) | ||
|
||
func getVerificationToken(r *resty.Client) (string, error) { | ||
resp, err := r. | ||
R(). | ||
Get(BaseURL) | ||
if err != nil { | ||
return "", err | ||
} | ||
doc, err := html.Parse(strings.NewReader(resp.String())) | ||
if err != nil { | ||
return "", err | ||
} | ||
verificationToken := "" | ||
var processHtml func(*html.Node) | ||
processHtml = func(n *html.Node) { | ||
if verificationToken != "" { | ||
return | ||
} | ||
if n.Type == html.ElementNode && n.Data == "input" { | ||
for _, a := range n.Attr { | ||
if a.Key == "name" && a.Val == "__RequestVerificationToken" { | ||
for _, a := range n.Attr { | ||
if a.Key == "value" { | ||
if verificationToken == "" { | ||
verificationToken = a.Val | ||
return | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
for c := n.FirstChild; c != nil; c = c.NextSibling { | ||
if verificationToken != "" { | ||
return | ||
} | ||
processHtml(c) | ||
} | ||
} | ||
|
||
processHtml(doc) | ||
return verificationToken, nil | ||
} |
Oops, something went wrong.