spark-server/internal/module/auth/logic/login_test.go

215 lines
6.6 KiB
Go

package logic
import (
"context"
"errors"
"strings"
"testing"
"spark-server/internal/infrastructure/id"
smslogic "spark-server/internal/module/sms/logic"
smsmodel "spark-server/internal/module/sms/model"
userlogic "spark-server/internal/module/user/logic"
usermodel "spark-server/internal/module/user/model"
"spark-server/internal/pkg/errs"
"spark-server/internal/pkg/reqctx"
)
func TestLoginServiceValidatesAndIssues(t *testing.T) {
j, err := NewJWT(&Options{Secret: "test-secret-test-secret", Issuer: "spark-im"})
if err != nil {
t.Fatal(err)
}
svc := testLoginService(j, StaticUser{Email: "test01@spark.com", UserID: 11, SparkID: "test01"})
ctx := reqctx.With(t.Context(), &reqctx.RequestContext{RealIP: "203.0.113.10"})
_, err = svc.Login(ctx, &LoginInput{Email: "test01@spark.com"})
app, ok := errs.AsAppError(err)
if !ok || app.Code != errs.ErrInvalidArgument.Code || !strings.Contains(app.Msg, "Please enter the verification code") {
t.Fatalf("validate %+v", err)
}
enCtx := reqctx.With(t.Context(), &reqctx.RequestContext{Language: "en"})
_, err = svc.Login(enCtx, &LoginInput{Email: "test01@spark.com"})
app, ok = errs.AsAppError(err)
if !ok || !strings.Contains(app.Msg, "Please enter the verification code") {
t.Fatalf("en validate %+v", err)
}
tok, err := svc.Login(ctx, &LoginInput{
Email: "test01@spark.com", VerifyCode: "any", AppID: testAppID,
DeviceType: 1, IDFV: "00000000-0000-0000-0000-000000000007",
})
if err != nil {
t.Fatal(err)
}
if tok.UserID != 11 || tok.DeviceID != testIOSDeviceID || tok.AppID != uint32(testAppID) ||
tok.AccessToken == "" || tok.RefreshToken == "" || tok.SparkID != "test01" {
t.Fatalf("%+v", tok)
}
}
func TestLoginServiceRefreshLogout(t *testing.T) {
j, err := NewJWT(&Options{Secret: "test-secret-test-secret", Issuer: "spark-im"})
if err != nil {
t.Fatal(err)
}
users := StaticUser{Email: "test01@spark.com", UserID: 11, SparkID: "test01"}
kicker := &kickRec{}
svc := testLoginService(j, users)
svc.Sessions = kicker
ctx := reqctx.With(t.Context(), &reqctx.RequestContext{RealIP: "203.0.113.10"})
tok, err := svc.Login(ctx, &LoginInput{
Email: "test01@spark.com", VerifyCode: "any", AppID: testAppID,
DeviceType: 1, IDFV: "00000000-0000-0000-0000-000000000007",
})
if err != nil {
t.Fatal(err)
}
refreshed, err := svc.Refresh(ctx, RefreshInput{RefreshToken: tok.RefreshToken})
if err != nil || refreshed.UserID != 11 {
t.Fatalf("refresh %+v %v", refreshed, err)
}
p, err := j.Authenticate(ctx, refreshed.AccessToken)
if err != nil {
t.Fatal(err)
}
outCtx := reqctx.With(ctx, &reqctx.RequestContext{
UserID: p.UserID, DeviceID: p.DeviceID, AppID: p.AppID, JTI: p.JTI, TokenExpires: p.ExpiresAt,
})
if err := svc.Logout(outCtx); err != nil {
t.Fatal(err)
}
if kicker.n != 1 {
t.Fatalf("kick %d", kicker.n)
}
if _, err := (*LoginService)(nil).Refresh(ctx, RefreshInput{RefreshToken: "x"}); !errors.Is(err, errs.ErrUnauthorized) {
t.Fatalf("nil refresh %v", err)
}
if err := svc.Logout(ctx); !errors.Is(err, errs.ErrUnauthorized) {
t.Fatalf("logout ctx %v", err)
}
}
func TestLoginServiceUnauthorized(t *testing.T) {
j, err := NewJWT(&Options{Secret: "test-secret-test-secret"})
if err != nil {
t.Fatal(err)
}
in := LoginInput{
Email: "ada@example.com", VerifyCode: "000000", AppID: testAppID,
DeviceType: 6, IDFV: "web-test-idfv",
}
svc := NewLoginService(j, StaticUser{Email: "ada@example.com", Code: "123456", UserID: 11})
svc.Devices = fixedDeviceRegistrar{id: 1}
if _, err = svc.Login(t.Context(), &in); !errors.Is(err, errs.ErrUnauthorized) {
t.Fatalf("bad code: %v", err)
}
in.VerifyCode = "123456"
svc.Devices = nil
if _, err = svc.Login(t.Context(), &in); !errors.Is(err, errs.ErrUnauthorized) {
t.Fatalf("nil devices: %v", err)
}
}
type kickRec struct{ n int }
func (k *kickRec) KickDevice(int64, uint64) { k.n++ }
func TestLoginRejectsInvalidAppID(t *testing.T) {
j, err := NewJWT(&Options{Secret: "test-secret-test-secret"})
if err != nil {
t.Fatal(err)
}
svc := testLoginService(j, StaticUser{Email: "test01@spark.com", UserID: 1})
ctx := t.Context()
_, err = svc.Login(ctx, &LoginInput{
Email: "test01@spark.com", VerifyCode: "x", AppID: 0,
DeviceType: 1, IDFV: "00000000-0000-0000-0000-000000000007",
})
if err == nil {
t.Fatal("expected missing app_id")
}
_, err = svc.Login(ctx, &LoginInput{
Email: "test01@spark.com", VerifyCode: "x", AppID: testAppID,
DeviceType: 1,
})
if err == nil {
t.Fatal("expected missing idfv")
}
}
func TestJWTTripleModeTwoApps(t *testing.T) {
ctx := context.Background()
pairs := NewMemoryPairsMode(PairKeyTriple)
j, err := NewJWT(&Options{Secret: "test-secret-test-secret", Pairs: pairs, PairKeyMode: PairKeyTriple})
if err != nil {
t.Fatal(err)
}
a, err := j.Issue(ctx, 1, testIOSDeviceID, 1)
if err != nil {
t.Fatal(err)
}
b, err := j.Issue(ctx, 1, testIOSDeviceID, 2)
if err != nil {
t.Fatal(err)
}
if a.RefreshToken == b.RefreshToken {
t.Fatal("different apps should have different refresh pairs")
}
if _, err := j.Refresh(ctx, a.RefreshToken); err != nil {
t.Fatal(err)
}
if _, err := j.Refresh(ctx, b.RefreshToken); err != nil {
t.Fatal(err)
}
}
func TestStaticAuthenticate(t *testing.T) {
s := Static{Token: "abc", UserID: 1, DeviceID: 2}
p, err := s.Authenticate(t.Context(), "abc")
if err != nil || p.UserID != 1 || p.DeviceID != 2 {
t.Fatalf("%+v %v", p, err)
}
if _, err := s.Authenticate(t.Context(), "no"); !errors.Is(err, errs.ErrUnauthorized) {
t.Fatalf("bad token %v", err)
}
}
func TestLoginServiceRecordsContextDevice(t *testing.T) {
j, err := NewJWT(&Options{Secret: "test-secret-test-secret", Issuer: "spark-im"})
if err != nil {
t.Fatal(err)
}
repo := usermodel.NewMemoryRepo()
idgen, err := id.New(1)
if err != nil {
t.Fatal(err)
}
store := smsmodel.NewMemoryStore()
smsSvc := smslogic.NewService(store, nil, nil, nil)
users := userlogic.NewService(repo, smsSvc, idgen, nil)
svc := testLoginService(j, users)
ctx := reqctx.With(t.Context(), &reqctx.RequestContext{
RealIP: "198.51.100.9", Language: "en", AppVersion: "2.1.0",
})
tok, err := svc.Login(ctx, &LoginInput{
Email: "test07@spark.com", VerifyCode: "x", AppID: testAppID,
DeviceType: 2, DeviceName: "Pixel", AndroidID: "spark-test-android-9",
})
if err != nil {
t.Fatal(err)
}
dev, err := repo.GetDevice(ctx, tok.UserID, testAndroidDeviceID)
if err != nil {
t.Fatal(err)
}
if dev.DeviceID != testAndroidDeviceID || dev.LastLoginAt.IsZero() {
t.Fatalf("%+v", dev)
}
logs := repo.LoginLogs()
if len(logs) == 0 || logs[len(logs)-1].DeviceType != "android" || logs[len(logs)-1].AppVersion != "2.1.0" || logs[len(logs)-1].Language != "en" {
t.Fatalf("logs %+v", logs)
}
}