diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000000..4052297f9c
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,29 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ - ci-test
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ tests:
+ name: Tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Set up Go
+ uses: actions/setup-go@v4
+ with:
+ go-version: 1.24
+
+ - name: Run tests with coverage
+ run: |
+ echo "'Tests' job: go test"
+ go test -cover ./...
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000000..b58b603fea
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000000..105ce2da2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/learn-cicd-starter.iml b/.idea/learn-cicd-starter.iml
new file mode 100644
index 0000000000..8388dbc88e
--- /dev/null
+++ b/.idea/learn-cicd-starter.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000000..812ab5a681
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000000..d0adac68cd
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000000..35eb1ddfbb
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index c2bec0368b..854e6efba6 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+[](https://github.com/robaweku-bit/learn-cicd-starter/actions)
# learn-cicd-starter (Notely)
This repo contains the starter code for the "Notely" application for the "Learn CICD" course on [Boot.dev](https://boot.dev).
@@ -21,3 +22,7 @@ go build -o notely && ./notely
*This starts the server in non-database mode.* It will serve a simple webpage at `http://localhost:8080`.
You do *not* need to set up a database or any interactivity on the webpage yet. Instructions for that will come later in the course!
+
+
+ev's Notely app."
+"robbieMYNAME's version of Boot.dev's Notely app."app."
diff --git a/internal/auth/auth.go b/internal/auth/auth.go
index f969aacf63..b86f4693fa 100644
--- a/internal/auth/auth.go
+++ b/internal/auth/auth.go
@@ -6,18 +6,30 @@ import (
"strings"
)
-var ErrNoAuthHeaderIncluded = errors.New("no authorization header included")
+// GetAPIKey extracts an API key from the Authorization header.
+// The expected format is: "Authorization: ApiKey "
+func GetAPIKey(r *http.Request) (string, error) {
+ const prefix = "ApiKey "
-// GetAPIKey -
-func GetAPIKey(headers http.Header) (string, error) {
- authHeader := headers.Get("Authorization")
+ // Get the Authorization header value
+ authHeader := r.Header.Get("Authorization")
if authHeader == "" {
- return "", ErrNoAuthHeaderIncluded
+ return "", errors.New("authorization header is missing")
}
- splitAuth := strings.Split(authHeader, " ")
- if len(splitAuth) < 2 || splitAuth[0] != "ApiKey" {
- return "", errors.New("malformed authorization header")
+
+ // Check for the correct prefix
+ if !strings.HasPrefix(authHeader, prefix) {
+ return "", errors.New("authorization header must start with 'ApiKey '")
}
- return splitAuth[1], nil
+ // Trim the prefix to get the actual key
+ apiKey := strings.TrimPrefix(authHeader, prefix)
+ apiKey = strings.TrimSpace(apiKey)
+
+ if apiKey == "" {
+ return "", errors.New("API key is missing after prefix")
+ }
+
+ return apiKey, nil
}
+
diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go
new file mode 100644
index 0000000000..dc2a67eeba
--- /dev/null
+++ b/internal/auth/auth_test.go
@@ -0,0 +1,82 @@
+package auth
+
+import (
+ "net/http"
+ "testing"
+)
+
+func TestGetAPIKey(t *testing.T) {
+ tests := []struct {
+ name string
+ authHeader string
+ wantKey string
+ wantErrSubstr string
+ }{
+ {
+ name: "missing header",
+ authHeader: "",
+ wantErrSubstr: "authorization header is missing",
+ },
+ {
+ name: "wrong prefix",
+ authHeader: "Bearer abc123",
+ wantErrSubstr: "must start with 'ApiKey '",
+ },
+ {
+ name: "empty key after prefix",
+ authHeader: "ApiKey ",
+ wantErrSubstr: "API key is missing",
+ },
+ {
+ name: "Valid key",
+ authHeader: "ApiKey my-secret-key",
+ wantKey: "my-secret-key", //correct
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ req, _ := http.NewRequest("GET", "/", nil)
+ if tc.authHeader != "" {
+ req.Header.Set("Authorization", tc.authHeader)
+ }
+
+ gotKey, err := GetAPIKey(req)
+
+ if tc.wantErrSubstr != "" {
+ if err == nil || !contains(err.Error(), tc.wantErrSubstr) {
+ t.Errorf("expected error containing %q, got %v", tc.wantErrSubstr, err)
+ }
+ return
+ }
+
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ if gotKey != tc.wantKey {
+ t.Errorf("expected key %q, got %q", tc.wantKey, gotKey)
+ }
+ })
+ }
+}
+
+func contains(s, substr string) bool {
+ return len(substr) == 0 || (len(s) >= len(substr) && stringContains(s, substr))
+}
+
+func stringContains(s, substr string) bool {
+ return len(substr) <= len(s) && (indexOf(s, substr) >= 0)
+}
+
+func indexOf(s, substr string) int {
+ for i := range s {
+ if len(s)-i < len(substr) {
+ return -1
+ }
+ if s[i:i+len(substr)] == substr {
+ return i
+ }
+ }
+ return -1
+}
+// trigger CI
diff --git a/middleware_auth.go b/middleware_auth.go
index 6cbe03f867..3aa872402f 100644
--- a/middleware_auth.go
+++ b/middleware_auth.go
@@ -7,22 +7,24 @@ import (
"github.com/bootdotdev/learn-cicd-starter/internal/database"
)
-type authedHandler func(http.ResponseWriter, *http.Request, database.User)
-
-func (cfg *apiConfig) middlewareAuth(handler authedHandler) http.HandlerFunc {
+func (cfg *apiConfig) middlewareAuth(
+ handler func(http.ResponseWriter, *http.Request, database.User),
+) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
- apiKey, err := auth.GetAPIKey(r.Header)
+ apiKey, err := auth.GetAPIKey(r)
if err != nil {
- respondWithError(w, http.StatusUnauthorized, "Couldn't find api key", err)
+ http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
+ // Look up the user in the database
user, err := cfg.DB.GetUser(r.Context(), apiKey)
if err != nil {
- respondWithError(w, http.StatusNotFound, "Couldn't get user", err)
+ http.Error(w, "invalid API key", http.StatusUnauthorized)
return
}
+ // Call the actual handler with the authenticated user
handler(w, r, user)
}
}