feat: sync host-based public app links
This commit is contained in:
parent
10de462191
commit
8cfb6f74c1
19 changed files with 375 additions and 26 deletions
|
|
@ -754,6 +754,16 @@ func normalizeStickersBotShortName(raw string) string {
|
|||
raw = strings.TrimPrefix(raw, "tg://addemoji?set=")
|
||||
if strings.Contains(raw, "://") {
|
||||
if parsed, err := url.Parse(raw); err == nil {
|
||||
query := parsed.Query()
|
||||
route := strings.Trim(parsed.Path, "/")
|
||||
if route == "" {
|
||||
route = strings.ToLower(parsed.Host)
|
||||
}
|
||||
if route == "addstickers" || route == "addemoji" {
|
||||
if shortName := query.Get("set"); shortName != "" {
|
||||
raw = shortName
|
||||
}
|
||||
}
|
||||
parts := strings.Split(strings.Trim(parsed.Path, "/"), "/")
|
||||
for i, part := range parts {
|
||||
if (part == "addstickers" || part == "addemoji") && i+1 < len(parts) {
|
||||
|
|
|
|||
|
|
@ -649,3 +649,19 @@ func (h *stickersBotHookRecorder) PushStickerSetsChanged(_ context.Context, user
|
|||
h.userID = userID
|
||||
h.kind = kind
|
||||
}
|
||||
|
||||
func TestNormalizeStickersBotShortNameAcceptsHostBasedAppLinks(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
raw string
|
||||
want string
|
||||
}{
|
||||
{raw: "telesrv://addstickers?set=Legacy_Pack", want: "legacy_pack"},
|
||||
{raw: "owpg://tenant.example.test/addstickers?set=Hosted_Pack", want: "hosted_pack"},
|
||||
{raw: "owpg://tenant.example.test/addemoji?set=Emoji_Pack", want: "emoji_pack"},
|
||||
{raw: "https://telesrv.net/addstickers/Web_Pack", want: "web_pack"},
|
||||
} {
|
||||
if got := normalizeStickersBotShortName(tc.raw); got != tc.want {
|
||||
t.Fatalf("normalizeStickersBotShortName(%q) = %q, want %q", tc.raw, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"unicode/utf8"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/links"
|
||||
"telesrv/internal/store"
|
||||
)
|
||||
|
||||
|
|
@ -36,6 +37,7 @@ var telegramLoginMatchCodePool = []string{
|
|||
type Config struct {
|
||||
Issuer string
|
||||
AppScheme string
|
||||
AppLinkBase string
|
||||
AllowHTTP bool
|
||||
ClientSecretPepper []byte
|
||||
SupportedSigningAlgorithms []domain.TelegramLoginSigningAlgorithm
|
||||
|
|
@ -48,7 +50,7 @@ type Service struct {
|
|||
store store.TelegramLoginStore
|
||||
sealer *CodeSealer
|
||||
issuer string
|
||||
appScheme string
|
||||
appLinks links.AppLinkBuilder
|
||||
allowHTTP bool
|
||||
clientSecretPepper []byte
|
||||
signingAlgorithms []domain.TelegramLoginSigningAlgorithm
|
||||
|
|
@ -66,8 +68,9 @@ func NewService(loginStore store.TelegramLoginStore, sealer *CodeSealer, cfg Con
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("telegram login issuer: %w", err)
|
||||
}
|
||||
if !validAppScheme(cfg.AppScheme) {
|
||||
return nil, fmt.Errorf("telegram login app scheme is invalid")
|
||||
appLinks, err := links.NewAppLinkBuilder(cfg.AppScheme, cfg.AppLinkBase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("telegram login app links: %w", err)
|
||||
}
|
||||
if cfg.RequestTTL == 0 {
|
||||
cfg.RequestTTL = defaultRequestTTL
|
||||
|
|
@ -92,7 +95,7 @@ func NewService(loginStore store.TelegramLoginStore, sealer *CodeSealer, cfg Con
|
|||
}
|
||||
}
|
||||
return &Service{
|
||||
store: loginStore, sealer: sealer, issuer: issuer, appScheme: strings.ToLower(cfg.AppScheme),
|
||||
store: loginStore, sealer: sealer, issuer: issuer, appLinks: appLinks,
|
||||
allowHTTP: cfg.AllowHTTP,
|
||||
clientSecretPepper: append([]byte(nil), cfg.ClientSecretPepper...),
|
||||
signingAlgorithms: append([]domain.TelegramLoginSigningAlgorithm(nil), cfg.SupportedSigningAlgorithms...),
|
||||
|
|
@ -669,7 +672,7 @@ func (s *Service) CreateAuthorization(ctx context.Context, params CreateAuthoriz
|
|||
if err != nil {
|
||||
return CreatedAuthorization{}, err
|
||||
}
|
||||
deepLink := s.appScheme + "://oauth?token=" + url.QueryEscape(requestToken)
|
||||
deepLink := s.appLinks.Build("oauth", url.Values{"token": []string{requestToken}})
|
||||
return CreatedAuthorization{Request: request, RequestToken: requestToken, BrowserToken: browserToken, DeepLink: deepLink}, nil
|
||||
}
|
||||
|
||||
|
|
@ -885,12 +888,14 @@ func (s *Service) deepLinkToken(rawURL string) (string, error) {
|
|||
if err != nil {
|
||||
return "", domain.ErrTelegramLoginURLInvalid
|
||||
}
|
||||
customOrCanonicalScheme := strings.EqualFold(u.Scheme, s.appScheme) || strings.EqualFold(u.Scheme, "tg")
|
||||
var token string
|
||||
switch {
|
||||
case customOrCanonicalScheme && strings.EqualFold(u.Host, "oauth") && u.Path == "":
|
||||
case s.appLinks.MatchesRoute(u, "oauth"):
|
||||
token, _ = singleQueryValue(query, "token")
|
||||
case customOrCanonicalScheme && strings.EqualFold(u.Host, "resolve") && u.Path == "":
|
||||
case strings.EqualFold(u.Scheme, "tg") && strings.EqualFold(u.Host, "oauth") && u.Path == "":
|
||||
token, _ = singleQueryValue(query, "token")
|
||||
case (s.appLinks.MatchesLegacyRoute(u, "resolve") ||
|
||||
(strings.EqualFold(u.Scheme, "tg") && strings.EqualFold(u.Host, "resolve") && u.Path == "")):
|
||||
domainValue, domainOK := singleQueryValue(query, "domain")
|
||||
startApp, startAppOK := singleQueryValue(query, "startapp")
|
||||
if domainOK && startAppOK && strings.EqualFold(domainValue, "oauth") {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
|
@ -78,10 +79,18 @@ func TestServiceClientCreationAndSecretRotationAreSingleWinner(t *testing.T) {
|
|||
}
|
||||
|
||||
func newTelegramLoginTestService(t *testing.T, now *time.Time) (*Service, *memory.TelegramLoginStore) {
|
||||
return newTelegramLoginTestServiceWithAlgorithms(t, now, nil)
|
||||
return newTelegramLoginTestServiceWithConfig(t, now, nil, "")
|
||||
}
|
||||
|
||||
func newTelegramLoginTestServiceWithAlgorithms(t *testing.T, now *time.Time, algorithms []domain.TelegramLoginSigningAlgorithm) (*Service, *memory.TelegramLoginStore) {
|
||||
return newTelegramLoginTestServiceWithConfig(t, now, algorithms, "")
|
||||
}
|
||||
|
||||
func newTelegramLoginTestServiceWithAppLinkBase(t *testing.T, now *time.Time, appLinkBase string) (*Service, *memory.TelegramLoginStore) {
|
||||
return newTelegramLoginTestServiceWithConfig(t, now, nil, appLinkBase)
|
||||
}
|
||||
|
||||
func newTelegramLoginTestServiceWithConfig(t *testing.T, now *time.Time, algorithms []domain.TelegramLoginSigningAlgorithm, appLinkBase string) (*Service, *memory.TelegramLoginStore) {
|
||||
t.Helper()
|
||||
key := make([]byte, 32)
|
||||
key[0] = 7
|
||||
|
|
@ -93,7 +102,7 @@ func newTelegramLoginTestServiceWithAlgorithms(t *testing.T, now *time.Time, alg
|
|||
pepper := make([]byte, 32)
|
||||
pepper[0] = 9
|
||||
service, err := NewService(loginStore, sealer, Config{
|
||||
Issuer: "https://oauth.telesrv.test", AppScheme: "telesrv",
|
||||
Issuer: "https://oauth.telesrv.test", AppScheme: "telesrv", AppLinkBase: appLinkBase,
|
||||
AllowHTTP: true, ClientSecretPepper: pepper,
|
||||
SupportedSigningAlgorithms: algorithms,
|
||||
Now: func() time.Time { return *now },
|
||||
|
|
@ -107,7 +116,7 @@ func newTelegramLoginTestServiceWithAlgorithms(t *testing.T, now *time.Time, alg
|
|||
func TestServiceAcceptsOfficialClientCanonicalOAuthDeepLinks(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Unix(1_780_000_000, 0).UTC()
|
||||
service, _ := newTelegramLoginTestService(t, &now)
|
||||
service, _ := newTelegramLoginTestServiceWithAppLinkBase(t, &now, "owpg://tenant.example.test")
|
||||
credentials, err := service.CreateClient(ctx, 9030, domain.TelegramLoginSigningRS256)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -132,8 +141,13 @@ func TestServiceAcceptsOfficialClientCanonicalOAuthDeepLinks(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
token := parsed.Query().Get("token")
|
||||
if got, want := parsed.Scheme+"://"+parsed.Host+parsed.Path, "owpg://tenant.example.test/oauth"; got != want {
|
||||
t.Fatalf("generated deep link root = %q, want %q", got, want)
|
||||
}
|
||||
valid := []string{
|
||||
created.DeepLink,
|
||||
"telesrv://oauth?token=" + url.QueryEscape(token),
|
||||
"telesrv://resolve?domain=oauth&startapp=" + url.QueryEscape(token),
|
||||
"tg://oauth?token=" + url.QueryEscape(token),
|
||||
"tg://resolve?domain=oauth&startapp=" + url.QueryEscape(token),
|
||||
"https://t.me/oauth?startapp=" + url.QueryEscape(token),
|
||||
|
|
@ -146,6 +160,9 @@ func TestServiceAcceptsOfficialClientCanonicalOAuthDeepLinks(t *testing.T) {
|
|||
}
|
||||
invalid := []string{
|
||||
"telegram://oauth?token=" + url.QueryEscape(token),
|
||||
"owpg://other.example.test/oauth?token=" + url.QueryEscape(token),
|
||||
"owpg://tenant.example.test/resolve?domain=oauth&startapp=" + url.QueryEscape(token),
|
||||
"owpg://tenant.example.test/oauth/extra?token=" + url.QueryEscape(token),
|
||||
"tg://oauth/path?token=" + url.QueryEscape(token),
|
||||
"tg://oauth?token=" + url.QueryEscape(token) + "&token=other",
|
||||
"tg://resolve?domain=oauth&domain=other&startapp=" + url.QueryEscape(token),
|
||||
|
|
@ -228,6 +245,9 @@ func TestServiceAuthorizationCodeFlowAndRevocation(t *testing.T) {
|
|||
if created.DeepLink == "" || created.Request.ID == 0 || len(created.Request.MatchCodes) != 5 {
|
||||
t.Fatalf("created authorization = %#v", created)
|
||||
}
|
||||
if !strings.HasPrefix(created.DeepLink, "telesrv://oauth?token=") {
|
||||
t.Fatalf("default deep link = %q, want legacy telesrv:// OAuth form", created.DeepLink)
|
||||
}
|
||||
if _, err := service.CheckMatchCode(ctx, created.DeepLink, created.Request.MatchCodes[0]); err == nil && created.Request.MatchCodes[0] != created.Request.MatchCode {
|
||||
t.Fatal("wrong match code unexpectedly accepted")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue