|
4 | 4 | package ably_test
|
5 | 5 |
|
6 | 6 | import (
|
| 7 | + "bytes" |
7 | 8 | "context"
|
8 | 9 | "encoding/base64"
|
| 10 | + "encoding/json" |
9 | 11 | "errors"
|
10 | 12 | "fmt"
|
| 13 | + "io" |
11 | 14 | "net/http"
|
12 | 15 | "net/url"
|
13 | 16 | "strings"
|
@@ -342,6 +345,164 @@ func TestAuth_RequestToken(t *testing.T) {
|
342 | 345 | }
|
343 | 346 | }
|
344 | 347 |
|
| 348 | +func TestAuth_JWT_Token_RSA8c(t *testing.T) { |
| 349 | + |
| 350 | + t.Run("Get JWT from echo server", func(t *testing.T) { |
| 351 | + app := ablytest.MustSandbox(nil) |
| 352 | + defer safeclose(t, app) |
| 353 | + jwt, err := app.CreateJwt(3*time.Second, false) |
| 354 | + assert.NoError(t, err) |
| 355 | + assert.True(t, strings.HasPrefix(jwt, "ey")) |
| 356 | + |
| 357 | + // JWT header assertions |
| 358 | + header := strings.Split(jwt, ".")[0] |
| 359 | + jwtToken, err := base64.RawStdEncoding.DecodeString(header) |
| 360 | + assert.NoError(t, err) |
| 361 | + var result map[string]interface{} |
| 362 | + err = json.Unmarshal(jwtToken, &result) |
| 363 | + assert.NoError(t, err) |
| 364 | + assert.Equal(t, "HS256", result["alg"]) |
| 365 | + assert.Equal(t, "JWT", result["typ"]) |
| 366 | + assert.Contains(t, result, "kid") |
| 367 | + assert.NoError(t, err) |
| 368 | + |
| 369 | + // JWT payload assertions |
| 370 | + payload := strings.Split(jwt, ".")[1] |
| 371 | + jwtToken, err = base64.RawStdEncoding.DecodeString(payload) |
| 372 | + assert.NoError(t, err) |
| 373 | + err = json.Unmarshal(jwtToken, &result) |
| 374 | + assert.NoError(t, err) |
| 375 | + assert.Contains(t, result, "iat") |
| 376 | + assert.Contains(t, result, "exp") |
| 377 | + |
| 378 | + // check expiry of 3 seconds |
| 379 | + assert.Equal(t, float64(3), result["exp"].(float64)-result["iat"].(float64)) |
| 380 | + }) |
| 381 | + |
| 382 | + t.Run("Should be able to use it as a token", func(t *testing.T) { |
| 383 | + app := ablytest.MustSandbox(nil) |
| 384 | + defer safeclose(t, app) |
| 385 | + jwt, err := app.CreateJwt(3*time.Second, false) |
| 386 | + assert.NoError(t, err) |
| 387 | + assert.True(t, strings.HasPrefix(jwt, "ey")) |
| 388 | + |
| 389 | + rec, optn := ablytest.NewHttpRecorder() |
| 390 | + rest, err := ably.NewREST( |
| 391 | + ably.WithToken(jwt), |
| 392 | + ably.WithEnvironment(app.Environment), |
| 393 | + optn[0], |
| 394 | + ) |
| 395 | + assert.NoError(t, err, "rest()=%v", err) |
| 396 | + |
| 397 | + _, err = rest.Stats().Pages(context.Background()) |
| 398 | + assert.NoError(t, err, "Stats()=%v", err) |
| 399 | + |
| 400 | + assert.Len(t, rec.Requests(), 1) |
| 401 | + assert.Len(t, rec.Responses(), 1) |
| 402 | + |
| 403 | + statsRequest := rec.Request(0) |
| 404 | + assert.Equal(t, "/stats", statsRequest.URL.Path) |
| 405 | + encodedToken := base64.StdEncoding.EncodeToString([]byte(jwt)) |
| 406 | + assert.Equal(t, "Bearer "+encodedToken, statsRequest.Header.Get("Authorization")) |
| 407 | + }) |
| 408 | + |
| 409 | + t.Run("RSA8g, RSA3d: Should be able to authenticate using authURL", func(t *testing.T) { |
| 410 | + app := ablytest.MustSandbox(nil) |
| 411 | + defer safeclose(t, app) |
| 412 | + |
| 413 | + rec, optn := ablytest.NewHttpRecorder() |
| 414 | + rest, err := ably.NewREST( |
| 415 | + ably.WithAuthURL(ablytest.CREATE_JWT_URL), |
| 416 | + ably.WithAuthParams(app.GetJwtAuthParams(30*time.Second, false)), |
| 417 | + ably.WithEnvironment(app.Environment), |
| 418 | + optn[0], |
| 419 | + ) |
| 420 | + assert.NoError(t, err, "rest()=%v", err) |
| 421 | + |
| 422 | + _, err = rest.Stats().Pages(context.Background()) |
| 423 | + assert.NoError(t, err, "Stats()=%v", err) |
| 424 | + |
| 425 | + assert.Len(t, rec.Requests(), 2) |
| 426 | + assert.Len(t, rec.Responses(), 2) |
| 427 | + |
| 428 | + // first request is jwt request |
| 429 | + jwtRequest := rec.Request(0).URL |
| 430 | + assert.Equal(t, ablytest.CREATE_JWT_URL, "https://"+jwtRequest.Host+jwtRequest.Path) |
| 431 | + // response is jwt token |
| 432 | + jwtResponse, err := io.ReadAll(rec.Response(0).Body) |
| 433 | + assert.NoError(t, err) |
| 434 | + assert.True(t, bytes.HasPrefix(jwtResponse, []byte("ey"))) |
| 435 | + |
| 436 | + // Second request is made to stats with given jwt token (base64 encoded) |
| 437 | + statsRequest := rec.Request(1) |
| 438 | + assert.Equal(t, "/stats", statsRequest.URL.Path) |
| 439 | + encodedToken := base64.StdEncoding.EncodeToString(jwtResponse) |
| 440 | + assert.Equal(t, "Bearer "+encodedToken, statsRequest.Header.Get("Authorization")) |
| 441 | + }) |
| 442 | + |
| 443 | + t.Run("RSA8g, RSA3d: Should be able to authenticate using authCallback", func(t *testing.T) { |
| 444 | + app := ablytest.MustSandbox(nil) |
| 445 | + defer safeclose(t, app) |
| 446 | + |
| 447 | + jwtToken := "" |
| 448 | + authCallback := ably.WithAuthCallback(func(ctx context.Context, tp ably.TokenParams) (ably.Tokener, error) { |
| 449 | + jwtTokenString, err := app.CreateJwt(time.Second*30, false) |
| 450 | + jwtToken = jwtTokenString |
| 451 | + if err != nil { |
| 452 | + t.Fatalf("Error creating JWT: %v", err) |
| 453 | + return nil, err |
| 454 | + } |
| 455 | + return ably.TokenString(jwtTokenString), nil |
| 456 | + }) |
| 457 | + |
| 458 | + rec, optn := ablytest.NewHttpRecorder() |
| 459 | + rest, err := ably.NewREST( |
| 460 | + ably.WithEnvironment(app.Environment), |
| 461 | + authCallback, |
| 462 | + optn[0], |
| 463 | + ) |
| 464 | + assert.NoError(t, err) |
| 465 | + |
| 466 | + _, err = rest.Stats().Pages(context.Background()) |
| 467 | + assert.NoError(t, err, "Stats()=%v", err) |
| 468 | + |
| 469 | + assert.Len(t, rec.Requests(), 1) |
| 470 | + assert.Len(t, rec.Responses(), 1) |
| 471 | + |
| 472 | + assert.True(t, strings.HasPrefix(jwtToken, "ey")) |
| 473 | + // Second request is made to stats with given jwt token (base64 encoded) |
| 474 | + statsRequest := rec.Request(0) |
| 475 | + assert.Equal(t, "/stats", statsRequest.URL.Path) |
| 476 | + encodedToken := base64.StdEncoding.EncodeToString([]byte(jwtToken)) |
| 477 | + assert.Equal(t, "Bearer "+encodedToken, statsRequest.Header.Get("Authorization")) |
| 478 | + }) |
| 479 | + |
| 480 | + t.Run("RSA4e, RSA4b: Should return error when JWT is invalid", func(t *testing.T) { |
| 481 | + app := ablytest.MustSandbox(nil) |
| 482 | + defer safeclose(t, app) |
| 483 | + |
| 484 | + rec, optn := ablytest.NewHttpRecorder() |
| 485 | + rest, err := ably.NewREST( |
| 486 | + ably.WithAuthURL(ablytest.CREATE_JWT_URL), |
| 487 | + ably.WithAuthParams(app.GetJwtAuthParams(30*time.Second, true)), |
| 488 | + ably.WithEnvironment(app.Environment), |
| 489 | + optn[0], |
| 490 | + ) |
| 491 | + assert.NoError(t, err, "rest()=%v", err) |
| 492 | + |
| 493 | + _, err = rest.Stats().Pages(context.Background()) |
| 494 | + var errorInfo *ably.ErrorInfo |
| 495 | + assert.Error(t, err) |
| 496 | + assert.ErrorAs(t, err, &errorInfo) |
| 497 | + assert.Equal(t, 40144, int(errorInfo.Code)) |
| 498 | + assert.Equal(t, 401, errorInfo.StatusCode) |
| 499 | + assert.Contains(t, err.Error(), "invalid JWT format") |
| 500 | + |
| 501 | + assert.Len(t, rec.Requests(), 2) |
| 502 | + assert.Len(t, rec.Responses(), 2) |
| 503 | + }) |
| 504 | +} |
| 505 | + |
345 | 506 | func TestAuth_ReuseClientID(t *testing.T) {
|
346 | 507 | opts := []ably.ClientOption{ably.WithUseTokenAuth(true)}
|
347 | 508 | app, client := ablytest.NewREST(opts...)
|
|
0 commit comments