feat: integrate creator hub douyin workflows
This commit is contained in:
@@ -32,10 +32,7 @@ const (
|
||||
worksEndpoint = "https://www.douyin.com/aweme/v1/web/aweme/post/"
|
||||
)
|
||||
|
||||
var (
|
||||
keyPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,127}$`)
|
||||
credentialKeyPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{0,63}/[A-Za-z0-9][A-Za-z0-9._/-]{0,126}$`)
|
||||
)
|
||||
var keyPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,127}$`)
|
||||
|
||||
var ErrInvalid = errors.New("invalid douyin connector input")
|
||||
|
||||
@@ -65,9 +62,9 @@ type Response struct {
|
||||
}
|
||||
|
||||
// Browser is the deliberately narrow contract the restricted browser control
|
||||
// plane must implement. It does not permit arbitrary CDP commands.
|
||||
// plane must implement. Login happens in the managed browser session; the
|
||||
// connector never injects stored credentials or cookies.
|
||||
type Browser interface {
|
||||
SetCookies(context.Context, []Cookie) error
|
||||
Get(context.Context, string) (Response, error)
|
||||
}
|
||||
|
||||
@@ -127,9 +124,7 @@ type Request struct {
|
||||
|
||||
func (connector Connector) Sync(ctx context.Context, request Request) (Result, error) {
|
||||
if connector.Browser == nil || connector.Store == nil || !keyPattern.MatchString(request.AccountID) ||
|
||||
!keyPattern.MatchString(request.PlatformAccountKey) ||
|
||||
(request.Credential.Provider != "os_keyring" && request.Credential.Provider != "secret_manager") ||
|
||||
!credentialKeyPattern.MatchString(request.Credential.Key) {
|
||||
!keyPattern.MatchString(request.PlatformAccountKey) {
|
||||
return Result{}, ErrInvalid
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
@@ -140,24 +135,7 @@ func (connector Connector) Sync(ctx context.Context, request Request) (Result, e
|
||||
if ctx.Err() != nil {
|
||||
return connector.stop(ctx, request.AccountID, StatePolicyHold, ReasonAuthInvalid, Evidence{Phase: "login"})
|
||||
}
|
||||
if connector.Secrets == nil {
|
||||
return connector.stop(ctx, request.AccountID, StatePolicyHold, ReasonAuthInvalid, Evidence{Phase: "login"})
|
||||
}
|
||||
credential, credentialErr := connector.Secrets.Resolve(ctx, request.Credential)
|
||||
if credentialErr != nil {
|
||||
return connector.stop(ctx, request.AccountID, StatePolicyHold, ReasonAuthInvalid, Evidence{Phase: "login"})
|
||||
}
|
||||
cookies, credentialErr := ParseCredential(credential)
|
||||
if credentialErr != nil {
|
||||
return connector.stop(ctx, request.AccountID, StatePolicyHold, ReasonAuthInvalid, Evidence{Phase: "login"})
|
||||
}
|
||||
if credentialErr = connector.Browser.SetCookies(ctx, cookies); credentialErr != nil {
|
||||
return connector.stop(ctx, request.AccountID, StateNeedsConfirmation, ReasonUnknown, Evidence{Phase: "login"})
|
||||
}
|
||||
identityResponse, err = connector.Browser.Get(ctx, identityEndpoint)
|
||||
if err != nil {
|
||||
return connector.stop(ctx, request.AccountID, StateNeedsConfirmation, ReasonUnknown, Evidence{Phase: "identity"})
|
||||
}
|
||||
return connector.stop(ctx, request.AccountID, StateNeedsConfirmation, ReasonUnknown, Evidence{Phase: "login"})
|
||||
}
|
||||
if state, reason := classify(identityResponse); state != "" {
|
||||
return connector.stop(ctx, request.AccountID, state, reason, Evidence{Phase: "identity", HTTPStatus: identityResponse.Status})
|
||||
|
||||
@@ -83,7 +83,7 @@ func TestSyncLogsInVerifiesIdentityAndReadsOwnWorks(t *testing.T) {
|
||||
store := &fakeStore{}
|
||||
resolveCalls := 0
|
||||
result, err := (Connector{Browser: browser, Secrets: fakeSecrets{value: []byte(credential), resolveCalls: &resolveCalls}, Store: store}).Sync(context.Background(), Request{
|
||||
AccountID: "account-a", PlatformAccountKey: "sec-a", Credential: secretReference,
|
||||
AccountID: "account-a", PlatformAccountKey: "sec-a",
|
||||
})
|
||||
if err != nil || result.State != StateSucceeded || !result.Evidence.IdentityVerified || result.Evidence.WorksSeen != 1 || !result.Evidence.HasMore {
|
||||
t.Fatalf("unexpected result: %#v err=%v", result, err)
|
||||
@@ -193,7 +193,7 @@ func TestSyncRejectsInvalidCredentialWithoutLeakingIt(t *testing.T) {
|
||||
result, err := (Connector{Browser: browser, Secrets: fakeSecrets{value: []byte(`{"cookies":[{"name":"sessionid","value":"secret","domain":"evil.example"}]}`)}, Store: store}).Sync(context.Background(), Request{
|
||||
AccountID: "account-a", PlatformAccountKey: "sec-a", Credential: secretReference,
|
||||
})
|
||||
if err != nil || result.State != StatePolicyHold || result.ReasonCode != ReasonAuthInvalid || len(browser.urls) != 1 || len(browser.cookies) != 0 || len(store.holds) != 1 {
|
||||
if err != nil || result.State != StateNeedsConfirmation || result.ReasonCode != ReasonUnknown || len(browser.urls) != 1 || len(browser.cookies) != 0 || len(store.holds) != 1 {
|
||||
t.Fatalf("unexpected invalid credential result: %#v browser=%#v holds=%#v err=%v", result, browser, store.holds, err)
|
||||
}
|
||||
encoded, _ := json.Marshal(result)
|
||||
@@ -208,8 +208,8 @@ func TestSyncStopsWhenSecretReferenceCannotResolve(t *testing.T) {
|
||||
result, err := (Connector{Browser: browser, Secrets: fakeSecrets{err: errors.New("secret unavailable")}, Store: store}).Sync(context.Background(), Request{
|
||||
AccountID: "account-a", PlatformAccountKey: "sec-a", Credential: secretReference,
|
||||
})
|
||||
if err != nil || result.State != StatePolicyHold || result.ReasonCode != ReasonAuthInvalid || len(browser.urls) != 1 || len(store.holds) != 1 {
|
||||
t.Fatalf("unavailable secret did not fail closed: result=%#v browser=%#v holds=%#v err=%v", result, browser, store.holds, err)
|
||||
if err != nil || result.State != StateNeedsConfirmation || result.ReasonCode != ReasonUnknown || len(browser.urls) != 1 || len(store.holds) != 1 {
|
||||
t.Fatalf("browser session did not fail closed without credential injection: result=%#v browser=%#v holds=%#v err=%v", result, browser, store.holds, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,11 +269,15 @@ func TestSyncHoldsWithCancelledRequestContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncRejectsNonSecretCredentialReference(t *testing.T) {
|
||||
_, err := (Connector{Browser: &fakeBrowser{}, Secrets: fakeSecrets{}, Store: &fakeStore{}}).Sync(context.Background(), Request{
|
||||
func TestSyncIgnoresLegacyCredentialReference(t *testing.T) {
|
||||
browser := &fakeBrowser{responses: []Response{
|
||||
{Status: 200, Body: identityBody("uid-a", "sec-a", "handle-a")},
|
||||
{Status: 200, Body: []byte(`{"status_code":0,"has_more":false,"aweme_list":[]}`)},
|
||||
}}
|
||||
result, err := (Connector{Browser: browser, Store: &fakeStore{}}).Sync(context.Background(), Request{
|
||||
AccountID: "account-a", PlatformAccountKey: "sec-a", Credential: SecretReference{Provider: "plain_text", Key: "raw-secret"},
|
||||
})
|
||||
if !errors.Is(err, ErrInvalid) {
|
||||
t.Fatalf("accepted non-secret credential reference: %v", err)
|
||||
if errors.Is(err, ErrInvalid) || result.State != StateSucceeded {
|
||||
t.Fatalf("legacy credential reference affected browser-session sync: result=%#v err=%v", result, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user