Skip to content

Commit

Permalink
Add Line OAuth provider (#1015)
Browse files Browse the repository at this point in the history
  • Loading branch information
pilcrowonpaper authored Aug 22, 2023
1 parent b54fca3 commit ca59d50
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .auri/$gva98mzr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
package: "@lucia-auth/oauth" # package name
type: "minor" # "major", "minor", "patch"
---

Add Line provider
8 changes: 8 additions & 0 deletions packages/oauth/src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ export type {
LichessUserAuth
} from "./lichess.js";

export { line } from "./line.js";
export type {
LineAuth,
LineTokens,
LineUser,
LineUserAuth
} from "./line.js";

export { linkedin } from "./linkedin.js";
export type {
LinkedinAuth,
Expand Down
133 changes: 133 additions & 0 deletions packages/oauth/src/providers/line.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import {
OAuth2ProviderAuth,
createOAuth2AuthorizationUrl,
validateOAuth2AuthorizationCode
} from "../core/oauth2.js";
import { decodeIdToken } from "../core/oidc.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 = "line";

export const line = <_Auth extends Auth = Auth>(
auth: _Auth,
config: Config
): LineAuth<_Auth> => {
return new LineAuth(auth, config);
};

export class LineAuth<_Auth extends Auth = Auth> extends OAuth2ProviderAuth<
LineUserAuth<_Auth>
> {
private config: Config;

constructor(auth: _Auth, config: Config) {
super(auth);

this.config = config;
}

public getAuthorizationUrl = async (): Promise<
readonly [url: URL, state: string]
> => {
const scopeConfig = this.config.scope ?? [];
return await createOAuth2AuthorizationUrl(
"https://access.line.me/oauth2/v2.1/authorize",
{
clientId: this.config.clientId,
redirectUri: this.config.redirectUri,
scope: ["profile", "openid", ...scopeConfig]
}
);
};

public validateCallback = async (
code: string
): Promise<LineUserAuth<_Auth>> => {
const lineTokens = await this.validateAuthorizationCode(code);
const lineUser = await getLineUser(
lineTokens.accessToken,
lineTokens.idToken
);
return new LineUserAuth(this.auth, lineUser, lineTokens);
};

private validateAuthorizationCode = async (
code: string
): Promise<LineTokens> => {
const tokens = await validateOAuth2AuthorizationCode<{
access_token: string;
expires_in: number;
refresh_token: string;
id_token: string;
}>(code, "https://api.line.me/oauth2/v2.1/token", {
clientId: this.config.clientId,
redirectUri: this.config.redirectUri,
clientPassword: {
authenticateWith: "client_secret",
clientSecret: this.config.clientSecret
}
});
return {
accessToken: tokens.access_token,
accessTokenExpiresIn: tokens.expires_in,
refreshToken: tokens.refresh_token,
idToken: tokens.id_token
};
};
}

export class LineUserAuth<
_Auth extends Auth = Auth
> extends ProviderUserAuth<_Auth> {
public lineTokens: LineTokens;
public lineUser: LineUser;

constructor(auth: _Auth, lineUser: LineUser, lineTokens: LineTokens) {
super(auth, PROVIDER_ID, lineUser.userId);

this.lineTokens = lineTokens;
this.lineUser = lineUser;
}
}

const getLineUser = async (
accessToken: string,
idToken: string
): Promise<LineUser> => {
const request = new Request("GET https://api.line.me/v2/profile", {
headers: {
Authorization: authorizationHeader("bearer", accessToken)
}
});
const partialLineUser = await handleRequest<Omit<LineUser, "email">>(request);
const idTokenClaims = decodeIdToken<{ email?: string }>(idToken);
return {
email: idTokenClaims.email ?? null,
...partialLineUser
};
};

export type LineTokens = {
accessToken: string;
accessTokenExpiresIn: number;
refreshToken: string;
idToken: string;
};

export type LineUser = {
userId: string;
displayName: string;
pictureUrl: string;
statusMessage: string;
email: string | null;
};

0 comments on commit ca59d50

Please sign in to comment.