package provider import ( "context" "fmt" "net/url" "strings" "apps/backend/internal/module/threads/domain" ) // FakeProvider implements OAuth without Meta (dev + tests). // AuthorizeURL points at callbackURL with code=fake&state=... type FakeProvider struct { // CallbackBase is full callback path e.g. http://127.0.0.1:8888/api/v1/threads-accounts/oauth/callback CallbackBase string // FailExchange forces TH-04 FailExchange bool // FailRefresh forces TH-07 FailRefresh bool // FixedUserID when set, Exchange always returns this Threads user id (reconnect tests) FixedUserID string // FixedUsername optional with FixedUserID FixedUsername string } func (f *FakeProvider) Name() string { return "fake" } func (f *FakeProvider) AuthorizeURL(state, redirectURI string) string { base := f.CallbackBase if base == "" { base = redirectURI } u, err := url.Parse(base) if err != nil { return base + "?code=fake&state=" + url.QueryEscape(state) } q := u.Query() q.Set("code", "fake") q.Set("state", state) u.RawQuery = q.Encode() return u.String() } func (f *FakeProvider) ExchangeCode(ctx context.Context, code, redirectURI string) (*domain.TokenPair, error) { _ = ctx _ = redirectURI if f.FailExchange || code == "fail" { return nil, domain.ErrOAuthExchange } if code != "fake" && !strings.HasPrefix(code, "fake") { // still accept any code in pure fake mode for flexibility } id := f.FixedUserID if id == "" { id = fmt.Sprintf("fake_%d", domain.NowNano()%1_000_000) } user := f.FixedUsername if user == "" { user = "threads_" + id } return &domain.TokenPair{ AccessToken: "fake-access-" + id + fmt.Sprintf("_%d", domain.NowNano()%10000), RefreshToken: "fake-refresh-" + id, ExpiresIn: 3600 * 24 * 60, // ~60d UserID: id, Username: user, DisplayName: "Threads " + user, AvatarURL: "", }, nil } func (f *FakeProvider) Refresh(ctx context.Context, refreshToken string) (*domain.TokenPair, error) { _ = ctx if f.FailRefresh || refreshToken == "" || strings.Contains(refreshToken, "bad") { return nil, domain.ErrRefreshFailed } id := "refreshed" if strings.HasPrefix(refreshToken, "fake-refresh-") { id = strings.TrimPrefix(refreshToken, "fake-refresh-") } return &domain.TokenPair{ AccessToken: "fake-access-" + id, RefreshToken: refreshToken, ExpiresIn: 3600 * 24 * 60, UserID: id, Username: "threads_" + id, DisplayName: "Threads " + id, }, nil } var _ domain.Provider = (*FakeProvider)(nil)