Skip to content

Instantly share code, notes, and snippets.

@melodyclue
Created November 2, 2023 07:54
Show Gist options
  • Save melodyclue/e0c20eb7a4c86959cce0cb09863e1151 to your computer and use it in GitHub Desktop.
Save melodyclue/e0c20eb7a4c86959cce0cb09863e1151 to your computer and use it in GitHub Desktop.
Chatwork OAuth
import {
OAuth2ProviderAuthWithPKCE,
createOAuth2AuthorizationUrlWithPKCE,
validateOAuth2AuthorizationCode
} from "../core/oauth2.js";
import { ProviderUserAuth } from "../core/provider.js";
import { handleRequest, authorizationHeader } from "../utils/request.js";
import type { Auth } from "lucia";
type Config = {
clientId: string;
clientSecret: string;
redirectUri: string;
scope?: string[];
};
const PROVIDER_ID = "chatwork";
export const chatwork = <_Auth extends Auth = Auth>(
auth: _Auth,
config: Config
): ChatworkAuth<_Auth> => {
return new ChatworkAuth(auth, config);
};
export class ChatworkAuth<
_Auth extends Auth = Auth
> extends OAuth2ProviderAuthWithPKCE<ChatworkUserAuth<_Auth>> {
private config: Config;
constructor(auth: _Auth, config: Config) {
super(auth);
this.config = config;
}
public getAuthorizationUrl = async (): Promise<
readonly [url: URL, codeVerifier: string, state: string]
> => {
const scopeConfig = this.config.scope ?? [];
return await createOAuth2AuthorizationUrlWithPKCE(
"https://www.chatwork.com/packages/oauth2/login.php",
{
clientId: this.config.clientId,
codeChallengeMethod: "S256",
scope: ["users.all:read", "users.profile.me:read", ...scopeConfig],
redirectUri: this.config.redirectUri
}
);
};
public validateCallback = async (
code: string,
code_verifier: string
): Promise<ChatworkUserAuth<_Auth>> => {
const chatworkTokens = await this.validateAuthorizationCode(
code,
code_verifier
);
const chatworkUser = await getChatworkUser(chatworkTokens.accessToken);
return new ChatworkUserAuth(this.auth, chatworkUser, chatworkTokens);
};
private validateAuthorizationCode = async (
code: string,
codeVerifier: string
): Promise<ChatworkTokens> => {
const tokens = await validateOAuth2AuthorizationCode<{
access_token: string;
refresh_token?: string;
}>(code, "https://oauth.chatwork.com/token", {
clientId: this.config.clientId,
redirectUri: this.config.redirectUri,
codeVerifier,
clientPassword: {
authenticateWith: "client_secret",
clientSecret: this.config.clientSecret
}
});
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token ?? null
};
};
}
export class ChatworkUserAuth<
_Auth extends Auth = Auth
> extends ProviderUserAuth<_Auth> {
public chatworkTokens: ChatworkTokens;
public chatworkUser: ChatworkUser;
constructor(
auth: _Auth,
chatworkUser: ChatworkUser,
chatworkTokens: ChatworkTokens
) {
super(auth, PROVIDER_ID, chatworkUser.id);
this.chatworkTokens = chatworkTokens;
this.chatworkUser = chatworkUser;
}
}
const getChatworkUser = async (accessToken: string): Promise<ChatworkUser> => {
const request = new Request("https://api.chatwork.com/v2/me", {
headers: {
Authorization: authorizationHeader("bearer", accessToken)
}
});
const chatworkUserResult = await handleRequest<{
data: ChatworkUser;
}>(request);
return chatworkUserResult.data;
};
export type ChatworkTokens = {
accessToken: string;
refreshToken: string | null;
};
export type ChatworkUser = {
id: string;
name: string;
username: string;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment