Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions go-jwt/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ RUN go mod download

# Copy the source code. Note the slash at the end, as explained in
# https://docs.docker.com/reference/dockerfile/#copy
ADD https://keploy-enterprise.s3.us-west-2.amazonaws.com/releases/latest/assets/go_freeze_time_arm64 /lib/keploy/go_freeze_time_arm64
ADD https://keploy-enterprise.s3.us-west-2.amazonaws.com/releases/latest/assets/go_freeze_time_amd64 /lib/keploy/go_freeze_time_amd64

#set suitable permissions
RUN chmod +x /lib/keploy/go_freeze_time_arm64
RUN chmod +x /lib/keploy/go_freeze_time_amd64

# run the binary
RUN /lib/keploy/go_freeze_time_arm64
RUN /lib/keploy/go_freeze_time_amd64
Comment on lines +12 to +18
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Dockerfile downloads and executes a binary (go_freeze_time_amd64) directly from a remote URL (https://keploy-enterprise.s3.us-west-2.amazonaws.com/releases/latest/assets/go_freeze_time_amd64) during the image build without any integrity verification or pinning to an immutable identifier. If that S3 asset (or the path behind latest) is compromised or replaced, an attacker can run arbitrary code in the build environment and tamper with the resulting image. To mitigate this supply-chain risk, fetch a versioned artifact pinned to an immutable identifier (e.g., content hash or exact version) and/or verify its integrity (checksum or signature) before execution, or vendor the tool into the repository instead of downloading it at build time.

Copilot uses AI. Check for mistakes.
COPY *.go ./

# Build
RUN CGO_ENABLED=0 GOOS=linux go build -o /jwt-go
RUN CGO_ENABLED=0 GOOS=linux go build -tags=faketime -o /jwt-go

EXPOSE 8000

Expand Down
42 changes: 28 additions & 14 deletions go-jwt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Install keploy via one-click:-
curl --silent -O -L https://keploy.io/install.sh && source install.sh
```

### Start the Postgres Database
### Start the MySQL Database

```zsh
docker compose up -d db
Expand All @@ -35,13 +35,13 @@ Once we have our binary file ready,this command will start the recording of API
sudo -E keploy record -c "./go-jwt"
```

Make API Calls using Hoppscotch, Postman or cURL command. Keploy with capture those calls to generate the test-suites containing testcases and data mocks.
Make API Calls using Hoppscotch, Postman or cURL command. Keploy will capture those calls to generate the test-suites containing testcases and data mocks.

#### Generate testcases

To genereate testcases we just need to make some API calls. You can use [Postman](https://www.postman.com/), [Hoppscotch](https://hoppscotch.io/), or simply `curl`
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo in "genereate" which should be "generate".

Suggested change
To genereate testcases we just need to make some API calls. You can use [Postman](https://www.postman.com/), [Hoppscotch](https://hoppscotch.io/), or simply `curl`
To generate testcases we just need to make some API calls. You can use [Postman](https://www.postman.com/), [Hoppscotch](https://hoppscotch.io/), or simply `curl`

Copilot uses AI. Check for mistakes.

1. Generate shortned url
1. Check Health

```bash
curl --request GET \
Expand All @@ -50,12 +50,12 @@ curl --request GET \
--header 'Host: localhost:8000' \
--header 'User-Agent: curl/7.81.0'
```
this will return the response.
```
This will return the response:
```json
{"status": "healthy"}
```

2. Fetch the Products
2. Generate a token
```bash
curl --request GET \
--url http://localhost:8000/generate-token \
Expand All @@ -64,13 +64,13 @@ curl --request GET \
--header 'Accept: */*'
```

we will get output:
You will get the following output:

```json
{"token":"<your_jwt_token>"}
```

3. Fetch a single product
3. Check the token

```sh
curl --request GET \
Expand All @@ -80,16 +80,32 @@ curl --request GET \
--header 'User-Agent: curl/7.81.0'
```

we will get output:-
You will get the following output:
```json
{"username" : "example_user"}
```

Now, since these API calls were captured as editable testcases and written to ``keploy/tests folder``. The keploy directory would also have `mocks` files that contains all the outputs.
4. Test the time-sensitive endpoint

This sample includes a script `test_time_endpoint.sh` to easily test an endpoint that depends on the current time.

First, make the script executable:
```sh
chmod +x test_time_endpoint.sh
```

Now, run the script. It will automatically use the current time for the API call.
```sh
./test_time_endpoint.sh
```

This will send a request to the `/check-time` endpoint and you should see a successful response:
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation is incomplete - the sentence "This will send a request to the /check-time endpoint and you should see a successful response:" appears to be cut off. It should either end with a period or continue with more details about what the response looks like.

Suggested change
This will send a request to the `/check-time` endpoint and you should see a successful response:
This will send a request to the `/check-time` endpoint and you should see a successful response.

Copilot uses AI. Check for mistakes.

Now, since these API calls were captured as editable testcases and written to the `keploy/tests` folder. The keploy directory would also have `mocks` files that contain all the outputs.

![Testcase](./img/testcase.png?raw=true)

Now let's run the test mode (in the mux-sql directory, not the Keploy directory).
Now let's run the test mode (in the go-jwt directory, not the Keploy directory).

### Run captured testcases

Expand All @@ -99,6 +115,4 @@ sudo -E keploy test -c "./go-jwt" --delay 10

Once done, you can see the Test Runs on the Keploy server, like this:

![Testrun](./img/testrun.png?raw=true)


![Testrun](./img/testrun.png?raw=true)
14 changes: 8 additions & 6 deletions go-jwt/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ version: '3.8'

services:
db:
image: postgres
container_name: postgres
image: mysql:8.0
container_name: mysql
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
MYSQL_USER: myuser
MYSQL_PASSWORD: mypassword
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: mydb
ports:
- "5432:5432"
- "3306:3306"
command: --default-authentication-plugin=mysql_native_password
2 changes: 1 addition & 1 deletion go-jwt/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-sql-driver/mysql v1.5.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.1.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
Expand Down
93 changes: 84 additions & 9 deletions go-jwt/main.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// Package main is the entry point for the JWT-based user authentication service
// using Gin framework and PostgreSQL database. It provides endpoints for
// health check, token generation, and token validation.
package main

Check warning on line 1 in go-jwt/main.go

View workflow job for this annotation

GitHub Actions / lint (go-jwt)

package-comments: should have a package comment (revive)

import (
"fmt"
"log"
"net/http"
"os"
"strconv"
"time"

"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"

// Change: Imported MySQL dialect instead of Postgres
_ "github.com/jinzhu/gorm/dialects/mysql"
)

var (
Expand All @@ -36,12 +37,30 @@
}

func initDB() {
dsn := "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable"
db, err = gorm.Open("postgres", dsn)
if err != nil {
log.Printf("Failed to connect to database: %s", err)
dsn := "myuser:mypassword@tcp(localhost:3306)/mydb?charset=utf8&parseTime=True&loc=Local&timeout=60s&readTimeout=60s"

Comment on lines +40 to +41
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The database credentials are hardcoded in the DSN string. While this may be acceptable for a sample/demo application, consider using environment variables for better security and flexibility across different environments. This would allow different configurations for development, testing, and production without code changes.

Suggested change
dsn := "myuser:mypassword@tcp(localhost:3306)/mydb?charset=utf8&parseTime=True&loc=Local&timeout=60s&readTimeout=60s"
dbUser := os.Getenv("DB_USER")
if dbUser == "" {
dbUser = "myuser"
}
dbPassword := os.Getenv("DB_PASSWORD")
if dbPassword == "" {
dbPassword = "mypassword"
}
dbHost := os.Getenv("DB_HOST")
if dbHost == "" {
dbHost = "localhost"
}
dbPort := os.Getenv("DB_PORT")
if dbPort == "" {
dbPort = "3306"
}
dbName := os.Getenv("DB_NAME")
if dbName == "" {
dbName = "mydb"
}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local&timeout=60s&readTimeout=60s",
dbUser, dbPassword, dbHost, dbPort, dbName)

Copilot uses AI. Check for mistakes.
var connectionErr error
// Attempt to connect 10 times, waiting 2 seconds between attempts
for i := 0; i < 10; i++ {
db, connectionErr = gorm.Open("mysql", dsn)
if connectionErr == nil {
// Success! Check if we can actually ping
if err := db.DB().Ping(); err == nil {
break
}
Comment on lines +48 to +50
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The retry loop doesn't update the error state when ping fails. If gorm.Open succeeds but db.DB().Ping() fails (lines 48-50), the loop continues but connectionErr remains nil. This means the error check at line 57 may pass even though the connection is not functional. The ping error should be captured and assigned to connectionErr.

Suggested change
if err := db.DB().Ping(); err == nil {
break
}
if pingErr := db.DB().Ping(); pingErr == nil {
break
}
// Treat ping failure as a connection error for retry logic
connectionErr = pingErr

Copilot uses AI. Check for mistakes.
}

log.Printf("Database not ready yet (Attempt %d/10)... Waiting...", i+1)
time.Sleep(2 * time.Second)
}

if connectionErr != nil {
log.Printf("Failed to connect to database after retries: %s", connectionErr)
log.Println("Ensure Docker is running and the MySQL container is ready.")
os.Exit(1)
}

log.Println("Successfully connected to the database!")
db.AutoMigrate(&User{})
}

Expand All @@ -55,7 +74,7 @@
// Normally, you'd get this from the request, but we're hardcoding it for simplicity
username := "example_user"
password := "example_password"

fmt.Println("here is the current time :", time.Now().Unix())
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statements should be removed before merging to production. Use proper logging with log.Printf() if this information is needed for operational purposes.

Copilot uses AI. Check for mistakes.
// Set token expiration time
expirationTime := time.Now().Add(5 * time.Minute)

Expand All @@ -82,6 +101,7 @@
if db.Where("username = ?", username).First(&user).RecordNotFound() {
user = User{Username: username, Password: password, Token: tokenString}
db.Create(&user)
fmt.Println("token getting saved :", user)
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statements should be removed before merging to production. Use proper logging with log.Printf() if this information is needed for operational purposes.

Copilot uses AI. Check for mistakes.
} else {
user.Password = password
user.Token = tokenString
Expand Down Expand Up @@ -135,8 +155,62 @@
c.JSON(http.StatusOK, gin.H{"username": claims.Username})
}

// CheckTimeHandler checks if a client-provided timestamp is within 1 second of the server time.
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states "within 1 second" but the code now checks for 1.5 seconds (line 198). Update the comment to reflect the actual behavior: "within 1.5 seconds".

Suggested change
// CheckTimeHandler checks if a client-provided timestamp is within 1 second of the server time.
// CheckTimeHandler checks if a client-provided timestamp is within 1.5 seconds of the server time.

Copilot uses AI. Check for mistakes.
// The timestamp should be provided as a Unix timestamp in the 'ts' query parameter.
func CheckTimeHandler(c *gin.Context) {
// 1. Get the timestamp string from the URL query parameter 'ts'
clientTimeStr := c.Query("ts")
if clientTimeStr == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing 'ts' query parameter"})
return
}

// 2. Parse the string into an integer (Unix timestamp)
clientTimestamp, err := strconv.ParseInt(clientTimeStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid timestamp format. Must be a Unix timestamp in seconds."})
return
}

// 3. Convert the integer timestamp to a time.Time object
clientTime := time.Unix(clientTimestamp, 0)
serverTime := time.Now()

// 4. Calculate the duration (difference) between server time and client time
diff := serverTime.Sub(clientTime)

// 5. Get the absolute value of the duration, since the client could be ahead or behind
if diff < 0 {
diff = -diff
}

log.Printf(
"Server Time: %s",
serverTime.String(),
)

log.Printf(
"Time difference: %s",
diff.String(),
)

// 6. Check if the difference is greater than 1.5 seconds
if diff > 1500*time.Millisecond {
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR title states "Changing the 1 seconds check to 1.5 seconds" but this PR contains significantly more changes including: migration from PostgreSQL to MySQL, architecture change from arm64 to amd64, new /check-time endpoint, retry logic for database connections, and documentation updates. The PR title should accurately reflect the scope of changes, or these changes should be split into separate PRs for better reviewability.

Copilot uses AI. Check for mistakes.
c.Status(http.StatusBadRequest)
return
}

time.Sleep(1 * time.Second)

Comment on lines +203 to +204
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The time.Sleep call introduces an unconditional 1-second delay in the handler, which will significantly impact performance and user experience. This sleep appears to serve no functional purpose and should be removed. If this was added for testing purposes, it should not be in production code.

Suggested change
time.Sleep(1 * time.Second)

Copilot uses AI. Check for mistakes.
// 7. If the check passes, send a 200 OK response
c.Status(http.StatusOK)
}

func main() {
// Give Docker a moment to spin up if running via compose,
// though strictly 2 seconds might not be enough for a cold MySQL boot.
time.Sleep(2 * time.Second)

initDB()
defer func() {
if err := db.Close(); err != nil {
Expand All @@ -150,6 +224,7 @@
router.GET("/health", HealthCheckHandler)
router.GET("/generate-token", GenerateTokenHandler)
router.GET("/check-token", CheckTokenHandler)
router.GET("/check-time", CheckTimeHandler)

err = router.Run(":8000")
if err != nil && err != http.ErrServerClosed {
Expand Down
30 changes: 30 additions & 0 deletions go-jwt/test_time_endpoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash

# This script sends a request to the /check-time endpoint.
# - If run without arguments, it uses the current Unix timestamp (should succeed).
# - If run with a number as an argument, it uses that number as the timestamp.

# --- Configuration ---
HOSTNAME="localhost"
PORT="8000"
ENDPOINT="/check-time"
# ---------------------

# Check if a command-line argument (a custom timestamp) was provided
if [ -n "$1" ]; then
# Use the provided argument as the timestamp
TIMESTAMP_TO_SEND="$1"
echo "Using provided timestamp: $TIMESTAMP_TO_SEND"
else
# No argument provided, get the current Unix timestamp
TIMESTAMP_TO_SEND=$(date +%s)
echo "Using current timestamp: $TIMESTAMP_TO_SEND"
fi

# Construct the full URL
URL="http://${HOSTNAME}:${PORT}${ENDPOINT}?ts=${TIMESTAMP_TO_SEND}"

# Send the request using curl and print the result
echo "Sending request to: ${URL}"
curl -s "${URL}" # The -s flag makes curl silent (no progress meter)
echo # Add a newline for cleaner terminal output
Loading