@logto/connector-apple 1.6.9 → 1.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -6
- package/lib/index.js +3 -2
- package/lib/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -11,13 +11,17 @@ The official Logto connector for Apple social sign-in.
|
|
|
11
11
|
- [Configure scope](#configure-scope)
|
|
12
12
|
- [Pitfalls of configuring scope](#pitfalls-of-configuring-scope)
|
|
13
13
|
- [Test Apple connector](#test-apple-connector)
|
|
14
|
+
- [Troubleshooting](#troubleshooting)
|
|
15
|
+
- [Apple caches your App ID and Services ID configuration](#apple-caches-your-app-id-and-services-id-configuration)
|
|
16
|
+
- [`invalid_client`](#invalid_client)
|
|
17
|
+
- [`invalid_request` with "Invalid web redirect url"](#invalid_request-with-invalid-web-redirect-url)
|
|
14
18
|
|
|
15
19
|
## Get started
|
|
16
20
|
|
|
17
21
|
If you don't know the concept of the connector or don't know how to add this connector to your Sign-in experience, please see [Logto tutorial](https://docs.logto.io/docs/tutorials/get-started/enable-social-sign-in).
|
|
18
22
|
|
|
19
23
|
> ℹ️ **Note**
|
|
20
|
-
>
|
|
24
|
+
>
|
|
21
25
|
> Apple sign-in is required for AppStore if you have other social sign-in methods in your app.
|
|
22
26
|
> Having Apple sign-in on Android devices is great if you also provide an Android app.
|
|
23
27
|
|
|
@@ -26,10 +30,21 @@ You need to enroll [Apple Developer Program](https://developer.apple.com/program
|
|
|
26
30
|
### Enable Sign in with Apple for your app
|
|
27
31
|
|
|
28
32
|
> ⚠️ **Caution**
|
|
29
|
-
>
|
|
33
|
+
>
|
|
30
34
|
> Even if you want to implement Sign in with Apple on a web app only, you still need to have an existing app that embraces the AppStore ecosystem (i.e., have a valid App ID).
|
|
31
35
|
|
|
32
|
-
You
|
|
36
|
+
You do not need Xcode for this, or for any other step in this guide. Everything can be done from the Apple Developer portal.
|
|
37
|
+
|
|
38
|
+
**Option A: Apple Developer portal (no Xcode needed)**
|
|
39
|
+
|
|
40
|
+
1. Visit [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list/bundleId) and make sure the filter dropdown on the top right is set to "App IDs".
|
|
41
|
+
2. Click the App ID you want to use, or click the "+" button next to "Identifiers" to register a new one.
|
|
42
|
+
3. On the "Capabilities" tab, check "Sign In with Apple". It is enabled as a primary App ID by default, which is what a Services ID groups under. Use the "Edit" button next to the capability to check or change this.
|
|
43
|
+
4. Click "Save" on the top right.
|
|
44
|
+
|
|
45
|
+
**Option B: Xcode**
|
|
46
|
+
|
|
47
|
+
Project settings -> Signing & Capabilities.
|
|
33
48
|
|
|
34
49
|

|
|
35
50
|
|
|
@@ -57,17 +72,19 @@ Enter the domain of your Logto instance without protocol and port, e.g., `your.l
|
|
|
57
72
|
Click "Next" then "Done" to close the modal. Click "Continue" on the top-right corner, then click "Save" to save your configuration.
|
|
58
73
|
|
|
59
74
|
> ⚠️ **Caution**
|
|
60
|
-
>
|
|
75
|
+
>
|
|
61
76
|
> Apple does NOT allow Return URLs with HTTP protocol and `localhost` domain.
|
|
62
|
-
>
|
|
77
|
+
>
|
|
63
78
|
> If you want to test locally, you need to edit `/etc/hosts` file to map localhost to a custom domain and set up a local HTTPS environment. [mkcert](https://github.com/FiloSottile/mkcert) can help you for setting up local HTTPS.
|
|
64
79
|
|
|
80
|
+
Finally, go back to the Apple connector in Logto Console and paste this identifier into the "Services ID" field, e.g., `io.logto.test`. This must be the Services ID you just configured, not your App ID (bundle ID).
|
|
81
|
+
|
|
65
82
|
## Configure scope
|
|
66
83
|
|
|
67
84
|
To get user's email from Apple, you need to configure the scope to include `email`. For both email and name, you can use `name email` as the scope. See [Apple official docs](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms#3332113) for more info.
|
|
68
85
|
|
|
69
86
|
> ℹ️ **Note**
|
|
70
|
-
>
|
|
87
|
+
>
|
|
71
88
|
> The user may choose to hide their email address from your app. In this case, you will not be able to retrieve the real email address. An email address like `random@privaterelay.appleid.com` will be returned instead.
|
|
72
89
|
|
|
73
90
|
### Pitfalls of configuring scope
|
|
@@ -83,3 +100,38 @@ See developer discussion [here](https://forums.developer.apple.com/forums/thread
|
|
|
83
100
|
## Test Apple connector
|
|
84
101
|
|
|
85
102
|
That's it. The Apple connector should be available in both web and native apps. Don't forget to [Enable connector in sign-in experience](https://docs.logto.io/docs/recipes/configure-connectors/social-connector/enable-social-sign-in/).
|
|
103
|
+
|
|
104
|
+
## Troubleshooting
|
|
105
|
+
|
|
106
|
+
### Apple caches your App ID and Services ID configuration
|
|
107
|
+
|
|
108
|
+
> ⚠️ **Caution**
|
|
109
|
+
>
|
|
110
|
+
> Apple caches the Sign in with Apple configuration of your identifiers on their side. Changes to an App ID or a Services ID are not always picked up immediately, and have been observed to take up to 24 hours to take effect.
|
|
111
|
+
|
|
112
|
+
This is a common reason why a configuration that looks entirely correct still fails. If you have checked everything below and it still does not work, wait and try again before changing anything else. Repeatedly editing the configuration does not speed up the refresh.
|
|
113
|
+
|
|
114
|
+
If it is still failing well after 24 hours, [contact Apple Developer Support](https://developer.apple.com/contact/) and ask them to refresh the cached configuration of your identifier. They can clear the stale entry on their side.
|
|
115
|
+
|
|
116
|
+
### `invalid_client`
|
|
117
|
+
|
|
118
|
+
Apple shows this when it cannot resolve your `client_id` into a usable Sign in with Apple web client. An unregistered or mismatched Return URL surfaces as a different error (see below), so `invalid_client` points at the identifier rather than at the redirect URI.
|
|
119
|
+
|
|
120
|
+
Check the following, in order:
|
|
121
|
+
|
|
122
|
+
1. **Wait for Apple's cache to refresh.** See the section above.
|
|
123
|
+
2. **Check the "Services ID" field in Logto.** It must hold the Services ID identifier, not your App ID (bundle ID). The App ID is not a valid `client_id` for the web flow.
|
|
124
|
+
3. **Make sure the Services ID configuration was saved.** Reopen the Services ID in the Apple Developer portal and confirm your domain and Return URL are still listed. If they are gone, the "Done" -> "Continue" -> "Save" sequence was not completed and nothing was persisted.
|
|
125
|
+
|
|
126
|
+
### `invalid_request` with "Invalid web redirect url"
|
|
127
|
+
|
|
128
|
+
Your Return URL is not registered on the Services ID, or it does not match exactly.
|
|
129
|
+
|
|
130
|
+
Logto builds the redirect URI from the domain that serves your sign-in experience, so if you use a custom domain, register both your default Logto domain and your custom domain, as "Domains and Subdomains" and again as "Return URLs":
|
|
131
|
+
|
|
132
|
+
| Domains and Subdomains | Return URLs |
|
|
133
|
+
| ----------------------- | ------------------------------------------------------- |
|
|
134
|
+
| `your-tenant.logto.app` | `https://your-tenant.logto.app/callback/<connector-id>` |
|
|
135
|
+
| `auth.your-domain.com` | `https://auth.your-domain.com/callback/<connector-id>` |
|
|
136
|
+
|
|
137
|
+
Copy the redirect URIs from the connector page in Logto Console instead of typing them by hand. The connector ID is a randomly generated string, and a single wrong character produces the same error.
|
package/lib/index.js
CHANGED
|
@@ -40,8 +40,9 @@ var defaultMetadata = {
|
|
|
40
40
|
key: "clientId",
|
|
41
41
|
type: ConnectorConfigFormItemType.Text,
|
|
42
42
|
required: true,
|
|
43
|
-
label: "
|
|
44
|
-
placeholder: "
|
|
43
|
+
label: "Services ID",
|
|
44
|
+
placeholder: "com.your-company.your-service",
|
|
45
|
+
tooltip: "The Services ID identifier registered in your Apple Developer account. This is not your app's App ID (bundle ID); using the App ID here makes Apple reject the sign-in request with an `invalid_client` error."
|
|
45
46
|
},
|
|
46
47
|
{
|
|
47
48
|
key: "scope",
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/constant.ts","../src/types.ts"],"sourcesContent":["import { assert } from '@silverhand/essentials';\n\nimport type {\n GetAuthorizationUri,\n GetUserInfo,\n GetConnectorConfig,\n CreateConnector,\n SocialConnector,\n} from '@logto/connector-kit';\nimport {\n ConnectorError,\n ConnectorErrorCodes,\n validateConfig,\n ConnectorType,\n jsonGuard,\n} from '@logto/connector-kit';\nimport { generateStandardId } from '@logto/shared/universal';\nimport { createRemoteJWKSet, jwtVerify } from 'jose';\n\nimport { defaultMetadata, jwksUri, issuer, authorizationEndpoint } from './constant.js';\nimport { appleConfigGuard, dataGuard } from './types.js';\n\nconst generateNonce = () => generateStandardId();\n\nconst getAuthorizationUri =\n (getConfig: GetConnectorConfig): GetAuthorizationUri =>\n async ({ state, redirectUri, scope }, setSession) => {\n const config = await getConfig(defaultMetadata.id);\n\n validateConfig(config, appleConfigGuard);\n\n const nonce = generateNonce();\n\n const queryParameters = new URLSearchParams({\n client_id: config.clientId,\n redirect_uri: redirectUri,\n scope: scope ?? config.scope ?? '',\n state,\n nonce,\n // https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms#3332113\n response_type: 'code id_token',\n response_mode: 'form_post',\n });\n\n assert(\n setSession,\n new ConnectorError(ConnectorErrorCodes.NotImplemented, {\n message: \"'setSession' is not implemented.\",\n })\n );\n await setSession({ nonce });\n\n return `${authorizationEndpoint}?${queryParameters.toString()}`;\n };\n\nconst getUserInfo =\n (getConfig: GetConnectorConfig): GetUserInfo =>\n async (data, getSession) => {\n const { id_token: idToken, user } = await authorizationCallbackHandler(data);\n\n if (!idToken) {\n throw new ConnectorError(ConnectorErrorCodes.SocialIdTokenInvalid);\n }\n\n const config = await getConfig(defaultMetadata.id);\n validateConfig(config, appleConfigGuard);\n\n const { clientId } = config;\n\n try {\n const { payload } = await jwtVerify(idToken, createRemoteJWKSet(new URL(jwksUri)), {\n issuer,\n audience: clientId,\n });\n\n if (payload.nonce) {\n // TODO @darcy: need to specify error code\n assert(\n getSession,\n new ConnectorError(ConnectorErrorCodes.NotImplemented, {\n message: \"'getSession' is not implemented.\",\n })\n );\n const { nonce: validationNonce } = await getSession();\n\n assert(\n validationNonce,\n new ConnectorError(ConnectorErrorCodes.General, {\n message: \"'nonce' not presented in session storage.\",\n })\n );\n\n assert(\n validationNonce === payload.nonce,\n new ConnectorError(ConnectorErrorCodes.SocialIdTokenInvalid, {\n message: \"IdToken validation failed due to 'nonce' mismatch.\",\n })\n );\n }\n\n if (!payload.sub) {\n throw new ConnectorError(ConnectorErrorCodes.SocialIdTokenInvalid);\n }\n\n return {\n id: payload.sub,\n // The `user` object is only available at the first sign-in. Didn't find this in Apple's\n // docs but it seems to be the case. Fallback to the `email` field in the ID token just in\n // case.\n // See desperate developer discussion here:\n // https://forums.developer.apple.com/forums/thread/132223\n email:\n user?.email ??\n (payload.email && payload.email_verified === true ? String(payload.email) : undefined),\n name: [user?.name?.firstName, user?.name?.lastName].filter(Boolean).join(' ') || undefined,\n rawData: jsonGuard.parse(data),\n };\n } catch {\n throw new ConnectorError(ConnectorErrorCodes.SocialIdTokenInvalid);\n }\n };\n\nconst authorizationCallbackHandler = async (parameterObject: unknown) => {\n const result = dataGuard.safeParse(parameterObject);\n\n if (!result.success) {\n throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(parameterObject));\n }\n\n return result.data;\n};\n\nconst createAppleConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {\n return {\n metadata: defaultMetadata,\n type: ConnectorType.Social,\n configGuard: appleConfigGuard,\n getAuthorizationUri: getAuthorizationUri(getConfig),\n getUserInfo: getUserInfo(getConfig),\n };\n};\n\nexport default createAppleConnector;\n","import type { ConnectorMetadata } from '@logto/connector-kit';\nimport { ConnectorPlatform, ConnectorConfigFormItemType } from '@logto/connector-kit';\n\n// https://appleid.apple.com/.well-known/openid-configuration\nexport const issuer = 'https://appleid.apple.com';\nexport const authorizationEndpoint = `${issuer}/auth/authorize`;\nexport const accessTokenEndpoint = `${issuer}/auth/token`;\nexport const jwksUri = `${issuer}/auth/keys`;\n\nexport const defaultMetadata: ConnectorMetadata = {\n id: 'apple-universal',\n target: 'apple',\n platform: ConnectorPlatform.Universal,\n name: {\n en: 'Apple',\n 'zh-CN': 'Apple',\n 'tr-TR': 'Apple',\n ko: 'Apple',\n },\n logo: './logo.svg',\n logoDark: './logo-dark.svg',\n description: {\n en: 'Apple is a multinational high-end provider of hardware and software.',\n 'zh-CN': 'Apple 是全球领先的高端消费者软硬件提供商。',\n 'tr-TR': 'Apple, çok uluslu bir üst düzey donanım ve yazılım sağlayıcısıdır.',\n ko: 'Apple은 하드웨어와 소프트웨어의 다국적 공급자 입니다.',\n },\n readme: './README.md',\n formItems: [\n {\n key: 'clientId',\n type: ConnectorConfigFormItemType.Text,\n required: true,\n label: 'Identifier',\n placeholder: '<your-registered-identifier>',\n },\n {\n key: 'scope',\n type: ConnectorConfigFormItemType.MultilineText,\n required: false,\n label: 'Scope',\n placeholder: 'email name',\n },\n ],\n};\n\nexport const defaultTimeout = 5000;\n","import { z } from 'zod';\n\nexport const appleConfigGuard = z.object({\n clientId: z.string(),\n scope: z.string().optional(),\n});\n\nexport type AppleConfig = z.infer<typeof appleConfigGuard>;\n\nconst stringToJson = () =>\n z.string().transform((value, ctx): z.ZodType<JSON> => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return JSON.parse(value);\n } catch {\n ctx.addIssue({ code: 'custom', message: 'Invalid JSON' });\n return z.NEVER;\n }\n });\n\n// https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/configuring_your_webpage_for_sign_in_with_apple#3331292\n// https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms#3332113\nexport const dataGuard = z.object({\n id_token: z.string(),\n user: stringToJson()\n .pipe(\n z\n .object({\n name: z\n .object({\n firstName: z.string(),\n lastName: z.string(),\n })\n .partial(),\n email: z.string(),\n })\n .partial()\n )\n .optional(),\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/constant.ts","../src/types.ts"],"sourcesContent":["import { assert } from '@silverhand/essentials';\n\nimport type {\n GetAuthorizationUri,\n GetUserInfo,\n GetConnectorConfig,\n CreateConnector,\n SocialConnector,\n} from '@logto/connector-kit';\nimport {\n ConnectorError,\n ConnectorErrorCodes,\n validateConfig,\n ConnectorType,\n jsonGuard,\n} from '@logto/connector-kit';\nimport { generateStandardId } from '@logto/shared/universal';\nimport { createRemoteJWKSet, jwtVerify } from 'jose';\n\nimport { defaultMetadata, jwksUri, issuer, authorizationEndpoint } from './constant.js';\nimport { appleConfigGuard, dataGuard } from './types.js';\n\nconst generateNonce = () => generateStandardId();\n\nconst getAuthorizationUri =\n (getConfig: GetConnectorConfig): GetAuthorizationUri =>\n async ({ state, redirectUri, scope }, setSession) => {\n const config = await getConfig(defaultMetadata.id);\n\n validateConfig(config, appleConfigGuard);\n\n const nonce = generateNonce();\n\n const queryParameters = new URLSearchParams({\n client_id: config.clientId,\n redirect_uri: redirectUri,\n scope: scope ?? config.scope ?? '',\n state,\n nonce,\n // https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms#3332113\n response_type: 'code id_token',\n response_mode: 'form_post',\n });\n\n assert(\n setSession,\n new ConnectorError(ConnectorErrorCodes.NotImplemented, {\n message: \"'setSession' is not implemented.\",\n })\n );\n await setSession({ nonce });\n\n return `${authorizationEndpoint}?${queryParameters.toString()}`;\n };\n\nconst getUserInfo =\n (getConfig: GetConnectorConfig): GetUserInfo =>\n async (data, getSession) => {\n const { id_token: idToken, user } = await authorizationCallbackHandler(data);\n\n if (!idToken) {\n throw new ConnectorError(ConnectorErrorCodes.SocialIdTokenInvalid);\n }\n\n const config = await getConfig(defaultMetadata.id);\n validateConfig(config, appleConfigGuard);\n\n const { clientId } = config;\n\n try {\n const { payload } = await jwtVerify(idToken, createRemoteJWKSet(new URL(jwksUri)), {\n issuer,\n audience: clientId,\n });\n\n if (payload.nonce) {\n // TODO @darcy: need to specify error code\n assert(\n getSession,\n new ConnectorError(ConnectorErrorCodes.NotImplemented, {\n message: \"'getSession' is not implemented.\",\n })\n );\n const { nonce: validationNonce } = await getSession();\n\n assert(\n validationNonce,\n new ConnectorError(ConnectorErrorCodes.General, {\n message: \"'nonce' not presented in session storage.\",\n })\n );\n\n assert(\n validationNonce === payload.nonce,\n new ConnectorError(ConnectorErrorCodes.SocialIdTokenInvalid, {\n message: \"IdToken validation failed due to 'nonce' mismatch.\",\n })\n );\n }\n\n if (!payload.sub) {\n throw new ConnectorError(ConnectorErrorCodes.SocialIdTokenInvalid);\n }\n\n return {\n id: payload.sub,\n // The `user` object is only available at the first sign-in. Didn't find this in Apple's\n // docs but it seems to be the case. Fallback to the `email` field in the ID token just in\n // case.\n // See desperate developer discussion here:\n // https://forums.developer.apple.com/forums/thread/132223\n email:\n user?.email ??\n (payload.email && payload.email_verified === true ? String(payload.email) : undefined),\n name: [user?.name?.firstName, user?.name?.lastName].filter(Boolean).join(' ') || undefined,\n rawData: jsonGuard.parse(data),\n };\n } catch {\n throw new ConnectorError(ConnectorErrorCodes.SocialIdTokenInvalid);\n }\n };\n\nconst authorizationCallbackHandler = async (parameterObject: unknown) => {\n const result = dataGuard.safeParse(parameterObject);\n\n if (!result.success) {\n throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(parameterObject));\n }\n\n return result.data;\n};\n\nconst createAppleConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {\n return {\n metadata: defaultMetadata,\n type: ConnectorType.Social,\n configGuard: appleConfigGuard,\n getAuthorizationUri: getAuthorizationUri(getConfig),\n getUserInfo: getUserInfo(getConfig),\n };\n};\n\nexport default createAppleConnector;\n","import type { ConnectorMetadata } from '@logto/connector-kit';\nimport { ConnectorPlatform, ConnectorConfigFormItemType } from '@logto/connector-kit';\n\n// https://appleid.apple.com/.well-known/openid-configuration\nexport const issuer = 'https://appleid.apple.com';\nexport const authorizationEndpoint = `${issuer}/auth/authorize`;\nexport const accessTokenEndpoint = `${issuer}/auth/token`;\nexport const jwksUri = `${issuer}/auth/keys`;\n\nexport const defaultMetadata: ConnectorMetadata = {\n id: 'apple-universal',\n target: 'apple',\n platform: ConnectorPlatform.Universal,\n name: {\n en: 'Apple',\n 'zh-CN': 'Apple',\n 'tr-TR': 'Apple',\n ko: 'Apple',\n },\n logo: './logo.svg',\n logoDark: './logo-dark.svg',\n description: {\n en: 'Apple is a multinational high-end provider of hardware and software.',\n 'zh-CN': 'Apple 是全球领先的高端消费者软硬件提供商。',\n 'tr-TR': 'Apple, çok uluslu bir üst düzey donanım ve yazılım sağlayıcısıdır.',\n ko: 'Apple은 하드웨어와 소프트웨어의 다국적 공급자 입니다.',\n },\n readme: './README.md',\n formItems: [\n {\n key: 'clientId',\n type: ConnectorConfigFormItemType.Text,\n required: true,\n label: 'Services ID',\n placeholder: 'com.your-company.your-service',\n tooltip:\n \"The Services ID identifier registered in your Apple Developer account. This is not your app's App ID (bundle ID); using the App ID here makes Apple reject the sign-in request with an `invalid_client` error.\",\n },\n {\n key: 'scope',\n type: ConnectorConfigFormItemType.MultilineText,\n required: false,\n label: 'Scope',\n placeholder: 'email name',\n },\n ],\n};\n\nexport const defaultTimeout = 5000;\n","import { z } from 'zod';\n\nexport const appleConfigGuard = z.object({\n clientId: z.string(),\n scope: z.string().optional(),\n});\n\nexport type AppleConfig = z.infer<typeof appleConfigGuard>;\n\nconst stringToJson = () =>\n z.string().transform((value, ctx): z.ZodType<JSON> => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return JSON.parse(value);\n } catch {\n ctx.addIssue({ code: 'custom', message: 'Invalid JSON' });\n return z.NEVER;\n }\n });\n\n// https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/configuring_your_webpage_for_sign_in_with_apple#3331292\n// https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms#3332113\nexport const dataGuard = z.object({\n id_token: z.string(),\n user: stringToJson()\n .pipe(\n z\n .object({\n name: z\n .object({\n firstName: z.string(),\n lastName: z.string(),\n })\n .partial(),\n email: z.string(),\n })\n .partial()\n )\n .optional(),\n});\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/connector-apple",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.10",
|
|
4
4
|
"description": "Apple web connector implementation.",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@silverhand/essentials": "^2.9.1",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"jose": "^6.2.3",
|
|
9
9
|
"snakecase-keys": "^8.0.1",
|
|
10
10
|
"zod": "3.24.3",
|
|
11
|
-
"@logto/connector-kit": "^5.1.
|
|
11
|
+
"@logto/connector-kit": "^5.1.2",
|
|
12
12
|
"@logto/shared": "^3.4.3"
|
|
13
13
|
},
|
|
14
14
|
"main": "./lib/index.js",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@silverhand/ts-config": "6.0.0",
|
|
47
47
|
"@types/node": "^22.14.0",
|
|
48
48
|
"@types/supertest": "^6.0.2",
|
|
49
|
-
"@vitest/coverage-v8": "^4.1.
|
|
49
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
50
50
|
"eslint": "^8.56.0",
|
|
51
51
|
"lint-staged": "^15.0.2",
|
|
52
52
|
"nock": "^14.0.3",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"supertest": "^7.0.0",
|
|
55
55
|
"tsup": "^8.5.1",
|
|
56
56
|
"typescript": "^5.5.3",
|
|
57
|
-
"vitest": "^4.1.
|
|
57
|
+
"vitest": "^4.1.11"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
60
|
"precommit": "lint-staged",
|