Skip to content

Commit eeeba78

Browse files
authored
feat(connector): add email support for x connector (#7827)
fixed #7809
1 parent ad4f9d6 commit eeeba78

File tree

5 files changed

+15
-3
lines changed

5 files changed

+15
-3
lines changed

.changeset/wicked-mails-peel.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@logto/connector-x": minor
3+
---
4+
5+
add email syncing to x connector
6+
7+
You can now add the `users.email` scope to sync users' email addresses from X accounts.

packages/connectors/connector-x/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ In your Logto connector configuration, fill out the following fields with the va
5151
- **clientId:** Your App's Client ID.
5252
- **clientSecret:** Your App's Client Secret.
5353

54-
`scope` is a space-delimited list of [scopes](https://docs.x.com/x-api/users/user-lookup-me). If not provided, the default scope is `tweet.read users.read`.
54+
`scope` accepts a space-delimited list of [scopes](https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code#scopes). If you omit it, the default `tweet.read users.read` is used, and those two scopes are always required. Add any others your app needs, for example, include `users.email` to sync the user’s email: `tweet.read users.read users.email`.
5555

5656
### Config types
5757

packages/connectors/connector-x/src/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ describe('getUserInfo', () => {
119119
expect(socialUserInfo).toStrictEqual({
120120
id: '1',
121121
name: 'monalisa',
122+
email: undefined,
122123
rawData: {
123124
data: {
124125
id: '1',

packages/connectors/connector-x/src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,10 @@ const getUserInfo =
113113
redirectUri
114114
);
115115

116+
const hasEmailScope = (config.scope ?? defaultScope).split(' ').includes('users.email');
117+
116118
const userInfo = await ky
117-
.get(userInfoEndpoint, {
119+
.get(`${userInfoEndpoint}${hasEmailScope ? '?user.fields=confirmed_email' : ''}`, {
118120
headers: {
119121
Authorization: `${token_type} ${access_token}`,
120122
},
@@ -128,12 +130,13 @@ const getUserInfo =
128130
}
129131

130132
const {
131-
data: { id, name },
133+
data: { id, name, confirmed_email: email },
132134
} = userInfoResult.data;
133135

134136
return {
135137
id,
136138
name: conditional(name),
139+
email: conditional(email),
137140
rawData: jsonGuard.parse(userInfo),
138141
};
139142
} catch (error: unknown) {

packages/connectors/connector-x/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const userInfoResponseGuard = z.object({
1212
data: z.object({
1313
id: z.string(),
1414
name: z.string().optional().nullable(),
15+
confirmed_email: z.string().optional().nullable(),
1516
}),
1617
});
1718

0 commit comments

Comments
 (0)