All checks were successful
Server Check Build (NixCN CMS) TeamCity build finished
Signed-off-by: Asai Neko <sugar@sne.moe>
329 lines
7.5 KiB
Go
329 lines
7.5 KiB
Go
package service_auth
|
|
|
|
import (
|
|
"context"
|
|
"net/url"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"nixcn-cms/data"
|
|
"nixcn-cms/internal/exception"
|
|
"nixcn-cms/testutil"
|
|
)
|
|
|
|
func newSvc() AuthService { return NewAuthService() }
|
|
|
|
func parseURI(t *testing.T, raw string) *url.URL {
|
|
t.Helper()
|
|
u, err := url.Parse(raw)
|
|
require.NoError(t, err)
|
|
return u
|
|
}
|
|
|
|
type tokenPair struct {
|
|
AccessToken string
|
|
RefreshToken string
|
|
}
|
|
|
|
// getTokensForUser runs the full magic → redirect → token flow and
|
|
// returns the issued token pair.
|
|
func getTokensForUser(t *testing.T, ctx context.Context, email string) tokenPair {
|
|
t.Helper()
|
|
svc := newSvc()
|
|
|
|
magicResult := svc.Magic(&MagicPayload{
|
|
Context: ctx,
|
|
Data: &MagicData{
|
|
ClientId: testutil.TestClientID,
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "s",
|
|
Email: email,
|
|
},
|
|
})
|
|
require.Equal(t, 200, magicResult.Common.HttpCode)
|
|
magicURI := parseURI(t, magicResult.Data.Uri)
|
|
|
|
redirectResult := svc.Redirect(&RedirectPayload{
|
|
Context: ctx,
|
|
Data: &RedirectData{
|
|
ClientId: testutil.TestClientID,
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "s",
|
|
Code: magicURI.Query().Get("code"),
|
|
},
|
|
})
|
|
require.Equal(t, 200, redirectResult.Common.HttpCode)
|
|
|
|
finalURI := parseURI(t, redirectResult.Data)
|
|
tokenResult := svc.Token(&TokenPayload{
|
|
Context: ctx,
|
|
Data: &TokenData{Code: finalURI.Query().Get("code")},
|
|
})
|
|
require.Equal(t, 200, tokenResult.Common.HttpCode)
|
|
|
|
return tokenPair{
|
|
AccessToken: tokenResult.Data.AccessToken,
|
|
RefreshToken: tokenResult.Data.RefreshToken,
|
|
}
|
|
}
|
|
|
|
// ---- Exchange ----
|
|
|
|
func TestExchangeSuccess(t *testing.T) {
|
|
testutil.Setup(t)
|
|
ctx := context.Background()
|
|
|
|
u := testutil.SeedUser(t, "exchange@example.com", 10)
|
|
testutil.SeedClient(t)
|
|
|
|
svc := newSvc()
|
|
result := svc.Exchange(&ExchangePayload{
|
|
Context: ctx,
|
|
UserId: u.UserId,
|
|
Data: &ExchangeData{
|
|
ClientId: testutil.TestClientID,
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "state123",
|
|
},
|
|
})
|
|
|
|
assert.Equal(t, 200, result.Common.HttpCode)
|
|
require.NotNil(t, result.Data)
|
|
assert.Contains(t, result.Data.RedirectUri, "code=")
|
|
}
|
|
|
|
func TestExchangeInvalidRedirectUri(t *testing.T) {
|
|
testutil.Setup(t)
|
|
ctx := context.Background()
|
|
|
|
u := testutil.SeedUser(t, "exchange2@example.com", 10)
|
|
testutil.SeedClient(t)
|
|
|
|
svc := newSvc()
|
|
result := svc.Exchange(&ExchangePayload{
|
|
Context: ctx,
|
|
UserId: u.UserId,
|
|
Data: &ExchangeData{
|
|
ClientId: testutil.TestClientID,
|
|
RedirectUri: "://invalid-url",
|
|
State: "s",
|
|
},
|
|
})
|
|
|
|
assert.Equal(t, 400, result.Common.HttpCode)
|
|
}
|
|
|
|
func TestExchangeUserNotFound(t *testing.T) {
|
|
testutil.Setup(t)
|
|
ctx := context.Background()
|
|
|
|
svc := newSvc()
|
|
result := svc.Exchange(&ExchangePayload{
|
|
Context: ctx,
|
|
UserId: uuid.New(),
|
|
Data: &ExchangeData{
|
|
ClientId: testutil.TestClientID,
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "s",
|
|
},
|
|
})
|
|
|
|
assert.Equal(t, 500, result.Common.HttpCode)
|
|
}
|
|
|
|
// ---- Magic (debug_mode = true, skips Turnstile and Email) ----
|
|
|
|
func TestMagicDebugMode(t *testing.T) {
|
|
testutil.Setup(t) // sets server.debug_mode = true
|
|
ctx := context.Background()
|
|
|
|
svc := newSvc()
|
|
result := svc.Magic(&MagicPayload{
|
|
Context: ctx,
|
|
Data: &MagicData{
|
|
ClientId: testutil.TestClientID,
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "state",
|
|
Email: "magic@example.com",
|
|
},
|
|
})
|
|
|
|
assert.Equal(t, 200, result.Common.HttpCode)
|
|
require.NotNil(t, result.Data)
|
|
assert.Contains(t, result.Data.Uri, "/app/api/v1/auth/redirect")
|
|
}
|
|
|
|
// ---- Redirect ----
|
|
|
|
func TestRedirectCreatesNewUser(t *testing.T) {
|
|
testutil.Setup(t)
|
|
ctx := context.Background()
|
|
|
|
testutil.SeedClient(t)
|
|
|
|
svc := newSvc()
|
|
magicResult := svc.Magic(&MagicPayload{
|
|
Context: ctx,
|
|
Data: &MagicData{
|
|
ClientId: testutil.TestClientID,
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "s",
|
|
Email: "newuser@example.com",
|
|
},
|
|
})
|
|
require.Equal(t, 200, magicResult.Common.HttpCode)
|
|
require.NotNil(t, magicResult.Data)
|
|
|
|
u := parseURI(t, magicResult.Data.Uri)
|
|
code := u.Query().Get("code")
|
|
require.NotEmpty(t, code)
|
|
|
|
result := svc.Redirect(&RedirectPayload{
|
|
Context: ctx,
|
|
Data: &RedirectData{
|
|
ClientId: testutil.TestClientID,
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "s",
|
|
Code: code,
|
|
},
|
|
})
|
|
|
|
assert.Equal(t, 200, result.Common.HttpCode)
|
|
assert.Contains(t, result.Data, "code=")
|
|
|
|
email := "newuser@example.com"
|
|
user, err := new(data.User).GetByEmail(ctx, &email)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "newuser@example.com", user.Email)
|
|
}
|
|
|
|
func TestRedirectInvalidCode(t *testing.T) {
|
|
testutil.Setup(t)
|
|
|
|
svc := newSvc()
|
|
result := svc.Redirect(&RedirectPayload{
|
|
Context: context.Background(),
|
|
Data: &RedirectData{
|
|
ClientId: testutil.TestClientID,
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "s",
|
|
Code: "bad-code",
|
|
},
|
|
})
|
|
|
|
assert.Equal(t, 403, result.Common.HttpCode)
|
|
}
|
|
|
|
func TestRedirectClientNotFound(t *testing.T) {
|
|
testutil.Setup(t)
|
|
ctx := context.Background()
|
|
|
|
svc := newSvc()
|
|
magicResult := svc.Magic(&MagicPayload{
|
|
Context: ctx,
|
|
Data: &MagicData{
|
|
ClientId: "no-such-client",
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "s",
|
|
Email: "x@example.com",
|
|
},
|
|
})
|
|
require.Equal(t, 200, magicResult.Common.HttpCode)
|
|
|
|
u := parseURI(t, magicResult.Data.Uri)
|
|
code := u.Query().Get("code")
|
|
|
|
result := svc.Redirect(&RedirectPayload{
|
|
Context: ctx,
|
|
Data: &RedirectData{
|
|
ClientId: "no-such-client",
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "s",
|
|
Code: code,
|
|
},
|
|
})
|
|
|
|
assert.Equal(t, 400, result.Common.HttpCode)
|
|
assert.Equal(t, exception.AuthRedirectClientNotFound, result.Common.Exception.Original)
|
|
}
|
|
|
|
// ---- Token ----
|
|
|
|
func TestTokenSuccess(t *testing.T) {
|
|
testutil.Setup(t)
|
|
ctx := context.Background()
|
|
|
|
testutil.SeedClient(t)
|
|
tokens := getTokensForUser(t, ctx, "token@example.com")
|
|
|
|
assert.NotEmpty(t, tokens.AccessToken)
|
|
assert.NotEmpty(t, tokens.RefreshToken)
|
|
}
|
|
|
|
func TestTokenInvalidCode(t *testing.T) {
|
|
testutil.Setup(t)
|
|
|
|
svc := newSvc()
|
|
result := svc.Token(&TokenPayload{
|
|
Context: context.Background(),
|
|
Data: &TokenData{Code: "invalid"},
|
|
})
|
|
|
|
assert.Equal(t, 403, result.Common.HttpCode)
|
|
}
|
|
|
|
// ---- Refresh ----
|
|
|
|
func TestRefreshSuccess(t *testing.T) {
|
|
testutil.Setup(t)
|
|
ctx := context.Background()
|
|
|
|
testutil.SeedClient(t)
|
|
tokens := getTokensForUser(t, ctx, "refresh@example.com")
|
|
|
|
svc := newSvc()
|
|
result := svc.Refresh(&RefreshPayload{
|
|
Context: ctx,
|
|
Data: &RefreshData{RefreshToken: tokens.RefreshToken},
|
|
})
|
|
|
|
assert.Equal(t, 200, result.Common.HttpCode)
|
|
require.NotNil(t, result.Data)
|
|
assert.NotEmpty(t, result.Data.AccessToken)
|
|
assert.NotEmpty(t, result.Data.RefreshToken)
|
|
assert.NotEqual(t, tokens.RefreshToken, result.Data.RefreshToken)
|
|
}
|
|
|
|
func TestRefreshInvalidToken(t *testing.T) {
|
|
testutil.Setup(t)
|
|
|
|
svc := newSvc()
|
|
result := svc.Refresh(&RefreshPayload{
|
|
Context: context.Background(),
|
|
Data: &RefreshData{RefreshToken: "bad-refresh"},
|
|
})
|
|
|
|
assert.Equal(t, 401, result.Common.HttpCode)
|
|
}
|
|
|
|
func TestRefreshTokenExpires(t *testing.T) {
|
|
mr := testutil.Setup(t)
|
|
ctx := context.Background()
|
|
testutil.SeedClient(t)
|
|
|
|
tokens := getTokensForUser(t, ctx, "expire@example.com")
|
|
|
|
mr.FastForward(7*24*time.Hour + time.Minute)
|
|
|
|
svc := newSvc()
|
|
result := svc.Refresh(&RefreshPayload{
|
|
Context: ctx,
|
|
Data: &RefreshData{RefreshToken: tokens.RefreshToken},
|
|
})
|
|
|
|
assert.Equal(t, 401, result.Common.HttpCode)
|
|
}
|