diff --git a/.github/workflows/branchtest.yml b/.github/workflows/branchtest.yml index d2b22a051..4dafd9d58 100644 --- a/.github/workflows/branchtest.yml +++ b/.github/workflows/branchtest.yml @@ -2,6 +2,11 @@ name: Deploy and Test Branch on: workflow_dispatch: + inputs: + branches: + description: 'Branch to deploy and test' + required: true + default: 'develop' pull_request: types: [opened, synchronize, reopened] branches: [develop] @@ -28,7 +33,7 @@ jobs: uses: actions/checkout@v4 with: repository: gravitl/netclient - ref: develop + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.branch || 'develop' }} - name: check if branch exists id: getbranch run: | @@ -45,6 +50,6 @@ jobs: needs: [getbranch, skip-check] with: netclientbranch: ${{ needs.getbranch.outputs.netclientbranch }} - netmakerbranch: ${{ github.head_ref }} + netmakerbranch: ${{ github.event_name == 'workflow_dispatch' && inputs.branch || github.head_ref }} tag: ${{ github.run_id }}-${{ github.run_attempt }} secrets: inherit diff --git a/.github/workflows/deletedroplets.yml b/.github/workflows/deletedroplets.yml index 63f258244..11577278a 100644 --- a/.github/workflows/deletedroplets.yml +++ b/.github/workflows/deletedroplets.yml @@ -37,7 +37,7 @@ jobs: - name: delete droplets if: success() || failure() run: | - sleep 5m + sleep 1m response=$(curl -X DELETE \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ @@ -56,8 +56,9 @@ jobs: echo "Failed to delete droplets. Status code: $status_code" exit 1 fi + sleep 1m env: - DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} + DIGITALOCEAN_TOKEN: ${{ secrets.DO_TEST_TOKEN }} TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} - name: mark server as available if: success() || failure() @@ -108,13 +109,28 @@ jobs: - name: delete droplets if: success() || failure() run: | - sleep 3h - curl -X DELETE \ + sleep 1m + response=$(curl -X DELETE \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ - "https://api.digitalocean.com/v2/droplets?tag_name=$TAG" + -w "\n%{http_code}" \ + "https://api.digitalocean.com/v2/droplets?tag_name=$TAG") + + status_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + echo "Response body: $body" + echo "Status code: $status_code" + + if [ "$status_code" -eq 204 ]; then + echo "Droplets deleted successfully" + else + echo "Failed to delete droplets. Status code: $status_code" + exit 1 + fi + sleep 1m env: - DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} + DIGITALOCEAN_TOKEN: ${{ secrets.DO_TEST_TOKEN }} TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} - name: mark server as available if: success() || failure() diff --git a/pro/controllers/users.go b/pro/controllers/users.go index c5076d3dd..c3bd9df40 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -206,6 +206,10 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { } for _, inviteeEmail := range inviteReq.UserEmails { // check if user with email exists, then ignore + if !email.IsValid(inviteeEmail) { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid email "+inviteeEmail), "badrequest")) + return + } _, err := logic.GetUser(inviteeEmail) if err == nil { // user exists already, so ignore @@ -228,6 +232,14 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { slog.Error("failed to parse to invite url", "error", err) return } + if servercfg.DeployedByOperator() { + u, err = url.Parse(fmt.Sprintf("%s/invite?tenant_id=%s&email=%s&invite_code=%s", + proLogic.GetAccountsUIHost(), url.QueryEscape(servercfg.GetNetmakerTenantID()), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) + if err != nil { + slog.Error("failed to parse to invite url", "error", err) + return + } + } invite.InviteURL = u.String() err = logic.InsertUserInvite(invite) if err != nil { diff --git a/pro/email/email.go b/pro/email/email.go index f6a3e80a8..7411ae3e5 100644 --- a/pro/email/email.go +++ b/pro/email/email.go @@ -2,6 +2,7 @@ package email import ( "context" + "regexp" "github.com/gravitl/netmaker/servercfg" ) @@ -52,3 +53,8 @@ type Notification struct { func GetClient() (e EmailSender) { return client } + +func IsValid(email string) bool { + emailRegex := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`) + return emailRegex.MatchString(email) +} diff --git a/pro/license.go b/pro/license.go index 633728d14..50825d8a3 100644 --- a/pro/license.go +++ b/pro/license.go @@ -20,6 +20,7 @@ import ( "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/netclient/ncutils" + proLogic "github.com/gravitl/netmaker/pro/logic" "github.com/gravitl/netmaker/servercfg" ) @@ -206,7 +207,7 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, bool req, err := http.NewRequest( http.MethodPost, - getAccountsHost()+"/api/v1/license/validate", + proLogic.GetAccountsHost()+"/api/v1/license/validate", bytes.NewReader(requestBody), ) if err != nil { @@ -255,17 +256,6 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, bool return nil, false, err } -func getAccountsHost() string { - switch servercfg.GetEnvironment() { - case "dev": - return accountsHostDevelopment - case "staging": - return accountsHostStaging - default: - return accountsHostProduction - } -} - func cacheResponse(response []byte) error { lrc := licenseResponseCache{ Body: response, diff --git a/pro/license_test.go b/pro/license_test.go index 12d15d3f8..549bac0cd 100644 --- a/pro/license_test.go +++ b/pro/license_test.go @@ -4,11 +4,13 @@ package pro import ( - "github.com/gravitl/netmaker/config" "testing" + + "github.com/gravitl/netmaker/config" + proLogic "github.com/gravitl/netmaker/pro/logic" ) -func Test_getAccountsHost(t *testing.T) { +func Test_GetAccountsHost(t *testing.T) { tests := []struct { name string envK string @@ -69,8 +71,8 @@ func Test_getAccountsHost(t *testing.T) { if tt.envK != "" { t.Setenv(tt.envK, tt.envV) } - if got := getAccountsHost(); got != tt.want { - t.Errorf("getAccountsHost() = %v, want %v", got, tt.want) + if got := proLogic.GetAccountsHost(); got != tt.want { + t.Errorf("GetAccountsHost() = %v, want %v", got, tt.want) } }) } diff --git a/pro/logic/security.go b/pro/logic/security.go index 508ac656e..fcc6d73cd 100644 --- a/pro/logic/security.go +++ b/pro/logic/security.go @@ -7,6 +7,27 @@ import ( "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/servercfg" +) + +// constants for accounts api hosts +const ( + // accountsHostDevelopment is the accounts api host for development environment + accountsHostDevelopment = "https://api.dev.accounts.netmaker.io" + // accountsHostStaging is the accounts api host for staging environment + accountsHostStaging = "https://api.staging.accounts.netmaker.io" + // accountsHostProduction is the accounts api host for production environment + accountsHostProduction = "https://api.accounts.netmaker.io" +) + +// constants for accounts UI hosts +const ( + // accountsUIHostDevelopment is the accounts UI host for development environment + accountsUIHostDevelopment = "https://account.dev.netmaker.io" + // accountsUIHostStaging is the accounts UI host for staging environment + accountsUIHostStaging = "https://account.staging.netmaker.io" + // accountsUIHostProduction is the accounts UI host for production environment + accountsUIHostProduction = "https://account.netmaker.io" ) func NetworkPermissionsCheck(username string, r *http.Request) error { @@ -186,3 +207,25 @@ func checkPermissionScopeWithReqMethod(scope models.RsrcPermissionScope, reqmeth } return errors.New("operation not permitted") } + +func GetAccountsHost() string { + switch servercfg.GetEnvironment() { + case "dev": + return accountsHostDevelopment + case "staging": + return accountsHostStaging + default: + return accountsHostProduction + } +} + +func GetAccountsUIHost() string { + switch servercfg.GetEnvironment() { + case "dev": + return accountsUIHostDevelopment + case "staging": + return accountsUIHostStaging + default: + return accountsUIHostProduction + } +} diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index d9218d22b..84c5987bb 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -380,7 +380,6 @@ func DeleteRole(rid models.UserRoleID, force bool) error { } } } - return database.DeleteRecord(database.USER_PERMISSIONS_TABLE_NAME, rid.String()) } diff --git a/pro/types.go b/pro/types.go index b4f1fe15b..ae31cafe0 100644 --- a/pro/types.go +++ b/pro/types.go @@ -7,16 +7,6 @@ import ( "fmt" ) -// constants for accounts api hosts -const ( - // accountsHostDevelopment is the accounts api host for development environment - accountsHostDevelopment = "https://api.dev.accounts.netmaker.io" - // accountsHostStaging is the accounts api host for staging environment - accountsHostStaging = "https://api.staging.accounts.netmaker.io" - // accountsHostProduction is the accounts api host for production environment - accountsHostProduction = "https://api.accounts.netmaker.io" -) - const ( license_cache_key = "license_response_cache" license_validation_err_msg = "invalid license"