mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
feat(connector): add kakao connector (#1826)
* feat(connector): add kakao connector * fix(connector): kakao when user profile is null bug fix * chore(connector): update kakao connector readme * chore(connector): delete changelog * chore(connector): delete unused lib * test(connector): update test * fix: pnpm lock * fix: fix test Co-authored-by: wangsijie <wangsijie@silverhand.io>
This commit is contained in:
parent
d952d8660d
commit
1f9e820eb6
18 changed files with 621 additions and 1 deletions
54
packages/connector-kakao/README.md
Normal file
54
packages/connector-kakao/README.md
Normal file
|
@ -0,0 +1,54 @@
|
|||
# Kakao Connector
|
||||
|
||||
The Kakao connector provides a succinct way for your application to use Kakao’s OAuth 2.0 authentication system.
|
||||
|
||||
**Table of contents**
|
||||
- [Set up a project in the Kakao Devlopers Console](#set-up-a-project-in-the-kakao-devlopers-console)
|
||||
- [Configure Kakao Login](#configure-kakao-login)
|
||||
- [Activate Kakao Login](#activate-kakao-login)
|
||||
- [Privacy Setting](#privacy-setting)
|
||||
- [Security Setting (Optional)](#security-setting-optional)
|
||||
- [Configure Logto](#configure-logto)
|
||||
- [Config types](#config-types)
|
||||
- [clientId](#clientid)
|
||||
- [clientSecret](#clientseceret)
|
||||
|
||||
## Set up a project in the Kakao Devlopers Console
|
||||
- Visit the [Kakao Developers Console](https://developers.kakao.com/console/app) and sign in with your Kakao account.
|
||||
- Click the **Add an application** to create new project or choose exist project.
|
||||
|
||||
## Configure Kakao Login
|
||||
|
||||
### Activate Kakao Login
|
||||
- Click the **Product Settings -> Kakao Login** from the menu.
|
||||
- Turn on `Kakao Login Activation`
|
||||
- Add below URL into `Redirect URI`
|
||||
- `http(s)://YOUR_URL/callback/kakao-universal`
|
||||
- (Please replace `YOUR_URL` with your `Logto` URL, and choose `http` or `https` on your situation.)
|
||||
|
||||
### Privacy Setting
|
||||
- Click the **Product Settings -> Kakao Login -> Consent Item** from the menu.
|
||||
- Change state of `Nickname`, `Profile image`, and `Email` to **Required consent** (You might not able to change `Email` to **Required consent** because of your project setting.)
|
||||
|
||||
|
||||
### Security Setting (Optional)
|
||||
- Click the **Product Settings -> Kakao Login -> Security** from the menu.
|
||||
- Click the `Client secret code` to generate secret code.
|
||||
- Change `Activation state` to Enable. (If you enable it, `secret code` is necessary.)
|
||||
|
||||
## Configure Logto
|
||||
|
||||
### Config types
|
||||
|
||||
| Name | Type |
|
||||
|--------------|---------|
|
||||
| clientId | string |
|
||||
| clientSecret | string? |
|
||||
|
||||
#### clientId
|
||||
`clientId` is `REST API key` of your project.
|
||||
(You can find it from `summary` of your project from Kakao developers console.)
|
||||
|
||||
#### clientSeceret
|
||||
`clientSecret` is `Secret Code` of your project.
|
||||
(Please check [Security Setting (Optional)](#security-setting-optional))
|
4
packages/connector-kakao/docs/config-template.json
Normal file
4
packages/connector-kakao/docs/config-template.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"clientId": "<client-id>",
|
||||
"clientSecret": "<client-secret>"
|
||||
}
|
7
packages/connector-kakao/jest.config.ts
Normal file
7
packages/connector-kakao/jest.config.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { Config, merge } from '@silverhand/jest-config';
|
||||
|
||||
const config: Config.InitialOptions = merge({
|
||||
setupFilesAfterEnv: ['jest-matcher-specific-error'],
|
||||
});
|
||||
|
||||
export default config;
|
7
packages/connector-kakao/logo.svg
Normal file
7
packages/connector-kakao/logo.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="2500" height="2500" viewBox="0 0 256 256">
|
||||
<path fill="#FFE812"
|
||||
d="M256 236c0 11.046-8.954 20-20 20H20c-11.046 0-20-8.954-20-20V20C0 8.954 8.954 0 20 0h216c11.046 0 20 8.954 20 20v216z"/>
|
||||
<path d="M128 36C70.562 36 24 72.713 24 118c0 29.279 19.466 54.97 48.748 69.477-1.593 5.494-10.237 35.344-10.581 37.689 0 0-.207 1.762.934 2.434s2.483.15 2.483.15c3.272-.457 37.943-24.811 43.944-29.04 5.995.849 12.168 1.29 18.472 1.29 57.438 0 104-36.712 104-82 0-45.287-46.562-82-104-82z"/>
|
||||
<path fill="#FFE812"
|
||||
d="M70.5 146.625c-3.309 0-6-2.57-6-5.73V105.25h-9.362c-3.247 0-5.888-2.636-5.888-5.875s2.642-5.875 5.888-5.875h30.724c3.247 0 5.888 2.636 5.888 5.875s-2.642 5.875-5.888 5.875H76.5v35.645c0 3.16-2.691 5.73-6 5.73zM123.112 146.547c-2.502 0-4.416-1.016-4.993-2.65l-2.971-7.778-18.296-.001-2.973 7.783c-.575 1.631-2.488 2.646-4.99 2.646a9.155 9.155 0 0 1-3.814-.828c-1.654-.763-3.244-2.861-1.422-8.52l14.352-37.776c1.011-2.873 4.082-5.833 7.99-5.922 3.919.088 6.99 3.049 8.003 5.928l14.346 37.759c1.826 5.672.236 7.771-1.418 8.532a9.176 9.176 0 0 1-3.814.827c-.001 0 0 0 0 0zm-11.119-21.056L106 108.466l-5.993 17.025h11.986zM138 145.75c-3.171 0-5.75-2.468-5.75-5.5V99.5c0-3.309 2.748-6 6.125-6s6.125 2.691 6.125 6v35.25h12.75c3.171 0 5.75 2.468 5.75 5.5s-2.579 5.5-5.75 5.5H138zM171.334 146.547c-3.309 0-6-2.691-6-6V99.5c0-3.309 2.691-6 6-6s6 2.691 6 6v12.896l16.74-16.74c.861-.861 2.044-1.335 3.328-1.335 1.498 0 3.002.646 4.129 1.772 1.051 1.05 1.678 2.401 1.764 3.804.087 1.415-.384 2.712-1.324 3.653l-13.673 13.671 14.769 19.566a5.951 5.951 0 0 1 1.152 4.445 5.956 5.956 0 0 1-2.328 3.957 5.94 5.94 0 0 1-3.609 1.211 5.953 5.953 0 0 1-4.793-2.385l-14.071-18.644-2.082 2.082v13.091a6.01 6.01 0 0 1-6.002 6.003z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
54
packages/connector-kakao/package.json
Normal file
54
packages/connector-kakao/package.json
Normal file
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"name": "@logto/connector-kakao",
|
||||
"version": "1.0.0-beta.6",
|
||||
"description": "Kakao connector implementation.",
|
||||
"main": "./lib/index.js",
|
||||
"exports": "./lib/index.js",
|
||||
"author": "Kyungyoon Kim. <ruddbs5302@gmail.com>",
|
||||
"license": "MPL-2.0",
|
||||
"private": true,
|
||||
"files": [
|
||||
"lib",
|
||||
"docs",
|
||||
"logo.svg",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"precommit": "lint-staged",
|
||||
"build": "rm -rf lib/ && tsc -p tsconfig.build.json",
|
||||
"dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput --incremental",
|
||||
"lint": "eslint --ext .ts src",
|
||||
"lint:report": "pnpm lint --format json --output-file report.json",
|
||||
"test": "jest",
|
||||
"test:coverage": "jest --coverage --silent",
|
||||
"prepack": "pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logto/connector-core": "^1.0.0-beta.5",
|
||||
"@silverhand/essentials": "^1.2.0",
|
||||
"@silverhand/jest-config": "1.0.0-rc.3",
|
||||
"got": "^11.8.2",
|
||||
"zod": "^3.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/types": "^28.1.3",
|
||||
"@silverhand/eslint-config": "1.0.0-rc.2",
|
||||
"@silverhand/ts-config": "1.0.0-rc.2",
|
||||
"@types/jest": "^28.1.6",
|
||||
"@types/node": "^16.3.1",
|
||||
"eslint": "^8.21.0",
|
||||
"jest": "^28.1.3",
|
||||
"jest-matcher-specific-error": "^1.0.0",
|
||||
"lint-staged": "^13.0.0",
|
||||
"nock": "^13.2.2",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.0.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "@silverhand"
|
||||
},
|
||||
"prettier": "@silverhand/eslint-config/.prettierrc"
|
||||
}
|
30
packages/connector-kakao/src/constant.ts
Normal file
30
packages/connector-kakao/src/constant.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { ConnectorMetadata, ConnectorPlatform, ConnectorType } from '@logto/connector-core';
|
||||
|
||||
export const authorizationEndpoint = 'https://kauth.kakao.com/oauth/authorize';
|
||||
export const accessTokenEndpoint = 'https://kauth.kakao.com/oauth/token';
|
||||
export const userInfoEndpoint = 'https://kapi.kakao.com/v2/user/me';
|
||||
|
||||
export const defaultMetadata: ConnectorMetadata = {
|
||||
id: 'kakao-universal',
|
||||
target: 'kakao',
|
||||
type: ConnectorType.Social,
|
||||
platform: ConnectorPlatform.Universal,
|
||||
name: {
|
||||
en: 'Kakao',
|
||||
'zh-CN': 'Kakao',
|
||||
'tr-TR': 'Kakao',
|
||||
'ko-KR': '카카오',
|
||||
},
|
||||
logo: './logo.svg',
|
||||
logoDark: null,
|
||||
description: {
|
||||
en: 'Kakao is the most famous social network servcie provider in South Korea',
|
||||
'zh-CN': 'Kakao is the most famous social network servcie provider in South Korea',
|
||||
'tr-TR': 'Kakao is the most famous social network servcie provider in South Korea',
|
||||
'ko-KR': '카카오는 한국에서 가장 유명한 SNS 서비스 제공자 입니다.',
|
||||
},
|
||||
readme: './README.md',
|
||||
configTemplate: './docs/config-template.json',
|
||||
};
|
||||
|
||||
export const defaultTimeout = 5000;
|
138
packages/connector-kakao/src/index.test.ts
Normal file
138
packages/connector-kakao/src/index.test.ts
Normal file
|
@ -0,0 +1,138 @@
|
|||
import { ConnectorError, ConnectorErrorCodes } from '@logto/connector-core';
|
||||
import nock from 'nock';
|
||||
|
||||
import createConnector, { getAccessToken } from '.';
|
||||
import { accessTokenEndpoint, authorizationEndpoint, userInfoEndpoint } from './constant';
|
||||
import { mockedConfig } from './mock';
|
||||
|
||||
const getConfig = jest.fn().mockResolvedValue(mockedConfig);
|
||||
|
||||
describe('kakao connector', () => {
|
||||
describe('getAuthorizationUri', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should get a valid authorizationUri with redirectUri and state', async () => {
|
||||
const connector = await createConnector({ getConfig });
|
||||
const authorizationUri = await connector.getAuthorizationUri({
|
||||
state: 'some_state',
|
||||
redirectUri: 'http://localhost:3000/callback',
|
||||
});
|
||||
expect(authorizationUri).toEqual(
|
||||
`${authorizationEndpoint}?client_id=%3Cclient-id%3E&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&response_type=code&state=some_state`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAccessToken', () => {
|
||||
afterEach(() => {
|
||||
nock.cleanAll();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should get an accessToken by exchanging with code', async () => {
|
||||
nock(accessTokenEndpoint).post('').reply(200, {
|
||||
access_token: 'access_token',
|
||||
scope: 'scope',
|
||||
token_type: 'token_type',
|
||||
});
|
||||
const { accessToken } = await getAccessToken(mockedConfig, {
|
||||
code: 'code',
|
||||
redirectUri: 'dummyRedirectUri',
|
||||
});
|
||||
expect(accessToken).toEqual('access_token');
|
||||
});
|
||||
|
||||
it('throws SocialAuthCodeInvalid error if accessToken not found in response', async () => {
|
||||
nock(accessTokenEndpoint)
|
||||
.post('')
|
||||
.reply(200, { access_token: '', scope: 'scope', token_type: 'token_type' });
|
||||
await expect(
|
||||
getAccessToken(mockedConfig, { code: 'code', redirectUri: 'dummyRedirectUri' })
|
||||
).rejects.toMatchError(new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserInfo', () => {
|
||||
beforeEach(() => {
|
||||
nock(accessTokenEndpoint).post('').reply(200, {
|
||||
access_token: 'access_token',
|
||||
scope: 'scope',
|
||||
token_type: 'token_type',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
nock.cleanAll();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should get valid SocialUserInfo', async () => {
|
||||
nock(userInfoEndpoint)
|
||||
.post('')
|
||||
.reply(200, {
|
||||
id: 1_234_567_890,
|
||||
kakao_account: {
|
||||
is_email_valid: true,
|
||||
email: 'ruddbs5302@gmail.com',
|
||||
profile: {
|
||||
nickname: 'pemassi',
|
||||
profile_image_url: 'https://github.com/images/error/octocat_happy.gif',
|
||||
is_default_image: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
const connector = await createConnector({ getConfig });
|
||||
const socialUserInfo = await connector.getUserInfo({
|
||||
code: 'code',
|
||||
redirectUri: 'redirectUri',
|
||||
});
|
||||
expect(socialUserInfo).toMatchObject({
|
||||
id: '1234567890',
|
||||
avatar: 'https://github.com/images/error/octocat_happy.gif',
|
||||
name: 'pemassi',
|
||||
email: 'ruddbs5302@gmail.com',
|
||||
});
|
||||
});
|
||||
|
||||
it('throws SocialAccessTokenInvalid error if remote response code is 401', async () => {
|
||||
nock(userInfoEndpoint).post('').reply(401);
|
||||
const connector = await createConnector({ getConfig });
|
||||
await expect(connector.getUserInfo({ code: 'code', redirectUri: '' })).rejects.toMatchError(
|
||||
new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid)
|
||||
);
|
||||
});
|
||||
|
||||
it('throws General error', async () => {
|
||||
nock(userInfoEndpoint).post('').reply(200, {
|
||||
sub: '1234567890',
|
||||
name: 'monalisa octocat',
|
||||
given_name: 'monalisa',
|
||||
family_name: 'octocat',
|
||||
picture: 'https://github.com/images/error/octocat_happy.gif',
|
||||
email: 'octocat@google.com',
|
||||
email_verified: true,
|
||||
locale: 'en',
|
||||
});
|
||||
const connector = await createConnector({ getConfig });
|
||||
await expect(
|
||||
connector.getUserInfo({
|
||||
error: 'general_error',
|
||||
error_description: 'General error encountered.',
|
||||
})
|
||||
).rejects.toMatchError(
|
||||
new ConnectorError(
|
||||
ConnectorErrorCodes.General,
|
||||
'{"error":"general_error","error_description":"General error encountered."}'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('throws unrecognized error', async () => {
|
||||
nock(userInfoEndpoint).post('').reply(500);
|
||||
const connector = await createConnector({ getConfig });
|
||||
await expect(connector.getUserInfo({ code: 'code', redirectUri: '' })).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
156
packages/connector-kakao/src/index.ts
Normal file
156
packages/connector-kakao/src/index.ts
Normal file
|
@ -0,0 +1,156 @@
|
|||
/**
|
||||
* The Implementation of OpenID Connect of Kakao.
|
||||
* https://developers.kakao.com/docs/latest/en/kakaologin/rest-api
|
||||
*/
|
||||
import {
|
||||
ConnectorError,
|
||||
ConnectorErrorCodes,
|
||||
CreateConnector,
|
||||
GetAuthorizationUri,
|
||||
GetConnectorConfig,
|
||||
GetUserInfo,
|
||||
SocialConnector,
|
||||
validateConfig,
|
||||
} from '@logto/connector-core';
|
||||
import { assert, conditional } from '@silverhand/essentials';
|
||||
import got, { HTTPError } from 'got';
|
||||
|
||||
import {
|
||||
accessTokenEndpoint,
|
||||
authorizationEndpoint,
|
||||
defaultMetadata,
|
||||
defaultTimeout,
|
||||
userInfoEndpoint,
|
||||
} from './constant';
|
||||
import {
|
||||
accessTokenResponseGuard,
|
||||
authResponseGuard,
|
||||
KakaoConfig,
|
||||
kakaoConfigGuard,
|
||||
userInfoResponseGuard,
|
||||
} from './types';
|
||||
|
||||
const getAuthorizationUri =
|
||||
(getConfig: GetConnectorConfig): GetAuthorizationUri =>
|
||||
async ({ state, redirectUri }) => {
|
||||
const config = await getConfig(defaultMetadata.id);
|
||||
validateConfig<KakaoConfig>(config, kakaoConfigGuard);
|
||||
|
||||
const queryParameters = new URLSearchParams({
|
||||
client_id: config.clientId,
|
||||
redirect_uri: redirectUri,
|
||||
response_type: 'code',
|
||||
state,
|
||||
});
|
||||
|
||||
return `${authorizationEndpoint}?${queryParameters.toString()}`;
|
||||
};
|
||||
|
||||
export const getAccessToken = async (
|
||||
config: KakaoConfig,
|
||||
codeObject: { code: string; redirectUri: string }
|
||||
) => {
|
||||
const { code, redirectUri } = codeObject;
|
||||
const { clientId, clientSecret } = config;
|
||||
|
||||
// Note:Need to decodeURIComponent on code
|
||||
// https://stackoverflow.com/questions/51058256/google-api-node-js-invalid-grant-malformed-auth-code
|
||||
const httpResponse = await got.post(accessTokenEndpoint, {
|
||||
form: {
|
||||
code: decodeURIComponent(code),
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
redirect_uri: redirectUri,
|
||||
grant_type: 'authorization_code',
|
||||
},
|
||||
timeout: defaultTimeout,
|
||||
});
|
||||
|
||||
const result = accessTokenResponseGuard.safeParse(JSON.parse(httpResponse.body));
|
||||
|
||||
if (!result.success) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error.message);
|
||||
}
|
||||
|
||||
const { access_token: accessToken } = result.data;
|
||||
|
||||
assert(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
|
||||
|
||||
return { accessToken };
|
||||
};
|
||||
|
||||
const getUserInfo =
|
||||
(getConfig: GetConnectorConfig): GetUserInfo =>
|
||||
// eslint-disable-next-line complexity
|
||||
async (data) => {
|
||||
const { code, redirectUri } = await authorizationCallbackHandler(data);
|
||||
const config = await getConfig(defaultMetadata.id);
|
||||
validateConfig<KakaoConfig>(config, kakaoConfigGuard);
|
||||
const { accessToken } = await getAccessToken(config, { code, redirectUri });
|
||||
|
||||
try {
|
||||
const httpResponse = await got.post(userInfoEndpoint, {
|
||||
headers: {
|
||||
authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
timeout: defaultTimeout,
|
||||
});
|
||||
|
||||
const result = userInfoResponseGuard.safeParse(JSON.parse(httpResponse.body));
|
||||
|
||||
if (!result.success) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error.message);
|
||||
}
|
||||
|
||||
const { id, kakao_account } = result.data;
|
||||
const { is_email_valid, email, profile } = kakao_account ?? {
|
||||
is_email_valid: null,
|
||||
profile: null,
|
||||
email: null,
|
||||
};
|
||||
|
||||
return {
|
||||
id: id.toString(),
|
||||
avatar: conditional(profile && !profile.is_default_image && profile.profile_image_url),
|
||||
email: conditional(is_email_valid && email),
|
||||
name: conditional(profile?.nickname),
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
return getUserInfoErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
const authorizationCallbackHandler = async (parameterObject: unknown) => {
|
||||
const result = authResponseGuard.safeParse(parameterObject);
|
||||
|
||||
if (!result.success) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(parameterObject));
|
||||
}
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getUserInfoErrorHandler = (error: unknown) => {
|
||||
if (error instanceof HTTPError) {
|
||||
const { statusCode, body: rawBody } = error.response;
|
||||
|
||||
if (statusCode === 401) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid);
|
||||
}
|
||||
|
||||
throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(rawBody));
|
||||
}
|
||||
|
||||
throw error;
|
||||
};
|
||||
|
||||
const createKakaoConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {
|
||||
return {
|
||||
metadata: defaultMetadata,
|
||||
configGuard: kakaoConfigGuard,
|
||||
getAuthorizationUri: getAuthorizationUri(getConfig),
|
||||
getUserInfo: getUserInfo(getConfig),
|
||||
};
|
||||
};
|
||||
|
||||
export default createKakaoConnector;
|
4
packages/connector-kakao/src/mock.ts
Normal file
4
packages/connector-kakao/src/mock.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export const mockedConfig = {
|
||||
clientId: '<client-id>',
|
||||
clientSecret: '<client-secret>',
|
||||
};
|
40
packages/connector-kakao/src/types.ts
Normal file
40
packages/connector-kakao/src/types.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const kakaoConfigGuard = z.object({
|
||||
clientId: z.string(),
|
||||
clientSecret: z.string().optional(),
|
||||
});
|
||||
|
||||
export type KakaoConfig = z.infer<typeof kakaoConfigGuard>;
|
||||
|
||||
export const accessTokenResponseGuard = z.object({
|
||||
access_token: z.string(),
|
||||
scope: z.string().optional(),
|
||||
token_type: z.string(),
|
||||
});
|
||||
|
||||
export type AccessTokenResponse = z.infer<typeof accessTokenResponseGuard>;
|
||||
|
||||
export const userInfoResponseGuard = z.object({
|
||||
id: z.number(),
|
||||
kakao_account: z
|
||||
.object({
|
||||
is_email_valid: z.boolean().optional(),
|
||||
email: z.string().optional(),
|
||||
profile: z
|
||||
.object({
|
||||
nickname: z.string().optional(),
|
||||
profile_image_url: z.string().optional(),
|
||||
is_default_image: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type UserInfoResponse = z.infer<typeof userInfoResponseGuard>;
|
||||
|
||||
export const authResponseGuard = z.object({
|
||||
code: z.string(),
|
||||
redirectUri: z.string(),
|
||||
});
|
12
packages/connector-kakao/tsconfig.base.json
Normal file
12
packages/connector-kakao/tsconfig.base.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "@silverhand/ts-config/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
9
packages/connector-kakao/tsconfig.build.json
Normal file
9
packages/connector-kakao/tsconfig.build.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "./tsconfig.base",
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.ts"
|
||||
]
|
||||
}
|
14
packages/connector-kakao/tsconfig.json
Normal file
14
packages/connector-kakao/tsconfig.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "./tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"node",
|
||||
"jest",
|
||||
"jest-matcher-specific-error"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"jest.config.ts"
|
||||
]
|
||||
}
|
7
packages/connector-kakao/tsconfig.test.json
Normal file
7
packages/connector-kakao/tsconfig.test.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"extends": "./tsconfig",
|
||||
"compilerOptions": {
|
||||
"isolatedModules": false,
|
||||
"allowJs": true
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logto/core",
|
||||
"version": "1.0.0-beta.6",
|
||||
"version": "1.0.0-beta.5",
|
||||
"description": "The open source identity solution.",
|
||||
"main": "build/index.js",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
|
@ -34,6 +34,7 @@
|
|||
"@logto/connector-twilio-sms": "^1.0.0-beta.6",
|
||||
"@logto/connector-wechat-native": "^1.0.0-beta.6",
|
||||
"@logto/connector-wechat-web": "^1.0.0-beta.6",
|
||||
"@logto/connector-kakao": "^1.0.0-beta.6",
|
||||
"@logto/phrases": "^1.0.0-beta.6",
|
||||
"@logto/schemas": "^1.0.0-beta.6",
|
||||
"@logto/shared": "^1.0.0-beta.6",
|
||||
|
|
|
@ -17,6 +17,7 @@ export const defaultConnectorPackages = [
|
|||
'@logto/connector-twilio-sms',
|
||||
'@logto/connector-wechat-web',
|
||||
'@logto/connector-wechat-native',
|
||||
'@logto/connector-kakao',
|
||||
];
|
||||
|
||||
const notImplemented = () => {
|
||||
|
|
|
@ -88,6 +88,12 @@ const wechatNativeConnector = {
|
|||
config: {},
|
||||
createdAt: 1_646_382_233_000,
|
||||
};
|
||||
const kakaoConnector = {
|
||||
id: 'kakao-universal',
|
||||
enabled: false,
|
||||
config: {},
|
||||
createdAt: 1_646_382_233_000,
|
||||
};
|
||||
|
||||
const connectors = [
|
||||
alipayConnector,
|
||||
|
@ -104,6 +110,7 @@ const connectors = [
|
|||
twilioSmsConnector,
|
||||
wechatConnector,
|
||||
wechatNativeConnector,
|
||||
kakaoConnector,
|
||||
];
|
||||
|
||||
const findAllConnectors = jest.fn(async () => connectors);
|
||||
|
|
75
pnpm-lock.yaml
generated
75
pnpm-lock.yaml
generated
|
@ -422,6 +422,45 @@ importers:
|
|||
prettier: 2.7.1
|
||||
typescript: 4.7.4
|
||||
|
||||
packages/connector-kakao:
|
||||
specifiers:
|
||||
'@jest/types': ^28.1.3
|
||||
'@logto/connector-core': ^1.0.0-beta.5
|
||||
'@silverhand/eslint-config': 1.0.0-rc.2
|
||||
'@silverhand/essentials': ^1.2.0
|
||||
'@silverhand/jest-config': 1.0.0-rc.3
|
||||
'@silverhand/ts-config': 1.0.0-rc.2
|
||||
'@types/jest': ^28.1.6
|
||||
'@types/node': ^16.3.1
|
||||
eslint: ^8.21.0
|
||||
got: ^11.8.2
|
||||
jest: ^28.1.3
|
||||
jest-matcher-specific-error: ^1.0.0
|
||||
lint-staged: ^13.0.0
|
||||
nock: ^13.2.2
|
||||
prettier: ^2.7.1
|
||||
typescript: ^4.7.4
|
||||
zod: ^3.14.3
|
||||
dependencies:
|
||||
'@logto/connector-core': link:../connector-core
|
||||
'@silverhand/essentials': 1.2.0
|
||||
'@silverhand/jest-config': 1.0.0-rc.3_bi2kohzqnxavgozw3csgny5hju
|
||||
got: 11.8.3
|
||||
zod: 3.14.3
|
||||
devDependencies:
|
||||
'@jest/types': 28.1.3
|
||||
'@silverhand/eslint-config': 1.0.0-rc.2_swk2g7ygmfleszo5c33j4vooni
|
||||
'@silverhand/ts-config': 1.0.0-rc.2_typescript@4.7.4
|
||||
'@types/jest': 28.1.6
|
||||
'@types/node': 16.11.12
|
||||
eslint: 8.21.0
|
||||
jest: 28.1.3_@types+node@16.11.12
|
||||
jest-matcher-specific-error: 1.0.0
|
||||
lint-staged: 13.0.0
|
||||
nock: 13.2.2
|
||||
prettier: 2.7.1
|
||||
typescript: 4.7.4
|
||||
|
||||
packages/connector-mock-email:
|
||||
specifiers:
|
||||
'@logto/connector-core': ^1.0.0-beta.6
|
||||
|
@ -828,6 +867,7 @@ importers:
|
|||
'@logto/connector-facebook': ^1.0.0-beta.6
|
||||
'@logto/connector-github': ^1.0.0-beta.6
|
||||
'@logto/connector-google': ^1.0.0-beta.6
|
||||
'@logto/connector-kakao': ^1.0.0-beta.6
|
||||
'@logto/connector-sendgrid-email': ^1.0.0-beta.6
|
||||
'@logto/connector-smtp': ^1.0.0-beta.6
|
||||
'@logto/connector-twilio-sms': ^1.0.0-beta.6
|
||||
|
@ -913,6 +953,7 @@ importers:
|
|||
'@logto/connector-facebook': link:../connector-facebook
|
||||
'@logto/connector-github': link:../connector-github
|
||||
'@logto/connector-google': link:../connector-google
|
||||
'@logto/connector-kakao': link:../connector-kakao
|
||||
'@logto/connector-sendgrid-email': link:../connector-sendgrid-mail
|
||||
'@logto/connector-smtp': link:../connector-smtp
|
||||
'@logto/connector-twilio-sms': link:../connector-twilio-sms
|
||||
|
@ -2317,6 +2358,7 @@ packages:
|
|||
pacote: 13.4.1
|
||||
semver: 7.3.7
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -2347,6 +2389,7 @@ packages:
|
|||
p-waterfall: 2.1.1
|
||||
semver: 7.3.7
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -2487,6 +2530,7 @@ packages:
|
|||
whatwg-url: 8.7.0
|
||||
yargs-parser: 20.2.4
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -2684,6 +2728,7 @@ packages:
|
|||
npm-registry-fetch: 9.0.0
|
||||
npmlog: 4.1.2
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -2713,6 +2758,7 @@ packages:
|
|||
pify: 5.0.0
|
||||
read-package-json: 3.0.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -2751,6 +2797,7 @@ packages:
|
|||
npmlog: 4.1.2
|
||||
tar: 6.1.11
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -2849,6 +2896,7 @@ packages:
|
|||
pacote: 13.4.1
|
||||
semver: 7.3.7
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: true
|
||||
|
@ -2894,6 +2942,7 @@ packages:
|
|||
'@npmcli/run-script': 3.0.2
|
||||
npmlog: 4.1.2
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -2995,6 +3044,7 @@ packages:
|
|||
slash: 3.0.0
|
||||
write-json-file: 4.3.0
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: true
|
||||
|
@ -3224,6 +3274,7 @@ packages:
|
|||
treeverse: 2.0.0
|
||||
walk-up-path: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -3259,6 +3310,8 @@ packages:
|
|||
promise-retry: 2.0.1
|
||||
semver: 7.3.7
|
||||
which: 2.0.2
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
dev: true
|
||||
|
||||
/@npmcli/installed-package-contents/1.0.7:
|
||||
|
@ -3289,6 +3342,7 @@ packages:
|
|||
pacote: 13.4.1
|
||||
semver: 7.3.7
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -3340,6 +3394,7 @@ packages:
|
|||
node-gyp: 9.0.0
|
||||
read-package-json-fast: 2.0.3
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -5981,6 +6036,8 @@ packages:
|
|||
ssri: 8.0.1
|
||||
tar: 6.1.11
|
||||
unique-filename: 1.1.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
dev: true
|
||||
|
||||
/cacache/16.1.0:
|
||||
|
@ -6005,6 +6062,8 @@ packages:
|
|||
ssri: 9.0.1
|
||||
tar: 6.1.11
|
||||
unique-filename: 1.1.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
dev: true
|
||||
|
||||
/cache-content-type/1.0.1:
|
||||
|
@ -10594,6 +10653,7 @@ packages:
|
|||
import-local: 3.1.0
|
||||
npmlog: 4.1.2
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: true
|
||||
|
@ -10627,6 +10687,7 @@ packages:
|
|||
npm-package-arg: 8.1.5
|
||||
npm-registry-fetch: 11.0.0
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -10640,6 +10701,7 @@ packages:
|
|||
semver: 7.3.7
|
||||
ssri: 8.0.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -10962,6 +11024,7 @@ packages:
|
|||
socks-proxy-agent: 6.1.1
|
||||
ssri: 9.0.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -10985,6 +11048,7 @@ packages:
|
|||
socks-proxy-agent: 5.0.1
|
||||
ssri: 8.0.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -11009,6 +11073,7 @@ packages:
|
|||
socks-proxy-agent: 6.1.1
|
||||
ssri: 8.0.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -11901,6 +11966,7 @@ packages:
|
|||
tar: 6.1.11
|
||||
which: 2.0.2
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -12086,6 +12152,7 @@ packages:
|
|||
minizlib: 2.1.2
|
||||
npm-package-arg: 8.1.5
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -12101,6 +12168,7 @@ packages:
|
|||
npm-package-arg: 9.0.2
|
||||
proc-log: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -12117,6 +12185,7 @@ packages:
|
|||
minizlib: 2.1.2
|
||||
npm-package-arg: 8.1.5
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -12517,6 +12586,7 @@ packages:
|
|||
ssri: 9.0.1
|
||||
tar: 6.1.11
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -13246,6 +13316,11 @@ packages:
|
|||
|
||||
/promise-inflight/1.0.1:
|
||||
resolution: {integrity: sha1-mEcocL8igTL8vdhoEputEsPAKeM=}
|
||||
peerDependencies:
|
||||
bluebird: '*'
|
||||
peerDependenciesMeta:
|
||||
bluebird:
|
||||
optional: true
|
||||
dev: true
|
||||
|
||||
/promise-retry/2.0.1:
|
||||
|
|
Loading…
Add table
Reference in a new issue