-
-
Notifications
You must be signed in to change notification settings - Fork 495
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: pilcrowOnPaper <[email protected]>
- Loading branch information
1 parent
c520b5d
commit b39f868
Showing
6 changed files
with
271 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
--- | ||
order:: 0 | ||
title: "Lichess" | ||
description: "Learn about using the Lichess provider in Lucia OAuth integration" | ||
--- | ||
|
||
OAuth integration for Lichess. Provider id is `lichess`. | ||
|
||
```ts | ||
import { lichess } from "@lucia-auth/oauth/providers"; | ||
import { auth } from "./lucia.js"; | ||
|
||
const lichessAuth = lichess(auth, config); | ||
``` | ||
|
||
## `lichess()` | ||
|
||
```ts | ||
const lichess: ( | ||
auth: Auth, | ||
config: { | ||
clientId: string; | ||
redirectUri: string; | ||
scope?: string[]; | ||
} | ||
) => LichessProvider; | ||
``` | ||
|
||
##### Parameter | ||
|
||
| name | type | description | optional | | ||
| ------------------ | ------------------------------------ | --------------------------------------- | :------: | | ||
| auth | [`Auth`](/reference/lucia-auth/auth) | Lucia instance | | | ||
| config.clientId | `string` | client id - choose any unique client id | | | ||
| config.redirectUri | `string` | redirect URI | | | ||
| config.scope | `string[]` | an array of scopes | ✓ | | ||
|
||
##### Returns | ||
|
||
| type | description | | ||
| ------------------------------------- | ---------------- | | ||
| [`LichessProvider`](#lichessprovider) | Lichess provider | | ||
|
||
## Interfaces | ||
|
||
### `LichessProvider` | ||
|
||
Satisfies [`LichessProvider`](/reference/oauth/oauthprovider). | ||
|
||
#### `getAuthorizationUrl()` | ||
|
||
Returns the authorization url for user redirection, a state and PKCE code verifier. The state and code verifier should be stored in a cookie and validated on callback. | ||
|
||
```ts | ||
const getAuthorizationUrl: () => Promise< | ||
[url: URL, state: string, codeVerifier: string] | ||
>; | ||
``` | ||
|
||
##### Returns | ||
|
||
| name | type | description | | ||
| -------------- | -------- | -------------------- | | ||
| `url` | `URL` | authorization url | | ||
| `state` | `string` | state parameter used | | ||
| `codeVerifier` | `string` | PKCE code verifier | | ||
|
||
#### `validateCallback()` | ||
|
||
Validates the callback code. Requires the PKCE code verifier generated with `getAuthorizationUrl()`. | ||
|
||
```ts | ||
const validateCallback: ( | ||
code: string, | ||
codeVerifier: string | ||
) => Promise<ProviderSession>; | ||
``` | ||
|
||
##### Parameter | ||
|
||
| name | type | description | | ||
| -------------- | -------- | --------------------------------------------------------- | | ||
| `code` | `string` | authorization code from callback | | ||
| `codeVerifier` | `string` | PKCE code verifier generated with `getAuthorizationUrl()` | | ||
|
||
##### Returns | ||
|
||
| type | | ||
| ------------------------------------- | | ||
| [`LichessUserAuth`](#lichessuserauth) | | ||
|
||
##### Errors | ||
|
||
Request errors are thrown as [`OAuthRequestError`](/reference/oauth/interfaces#oauthrequesterror). | ||
|
||
### `LichessUserAuth` | ||
|
||
```ts | ||
type LichessUserAuth = ProviderUserAuth & { | ||
lichessUser: LichessUser; | ||
lichessTokens: LichessTokens; | ||
}; | ||
``` | ||
|
||
| type | | ||
| ------------------------------------------------------------------ | | ||
| [`ProviderUserAuth`](/reference/oauth/interfaces#provideruserauth) | | ||
| [`LichessUser`](#lichessuser) | | ||
| [`LichessTokens`](#lichesstokens) | | ||
|
||
### `LichessTokens` | ||
|
||
```ts | ||
type LichessTokens = { | ||
accessToken: string; | ||
accessTokenExpiresIn: string; | ||
}; | ||
``` | ||
|
||
## `LichessUser` | ||
|
||
```ts | ||
type LichessUser = { | ||
id: string; | ||
username: string; | ||
}; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { createUrl, handleRequest, authorizationHeaders } from "../request.js"; | ||
import { | ||
scope, | ||
generateState, | ||
providerUserAuth, | ||
encodeBase64 | ||
} from "../core.js"; | ||
import { generateRandomString } from "lucia/utils"; | ||
|
||
import type { Auth } from "lucia"; | ||
import type { OAuthConfig, OAuthProvider } from "../core.js"; | ||
|
||
type Config = OAuthConfig & { | ||
redirectUri: string; | ||
}; | ||
|
||
const PROVIDER_ID = "lichess"; | ||
|
||
export const lichess = <_Auth extends Auth>(auth: _Auth, config: Config) => { | ||
const getAuthorizationUrl = async () => { | ||
const state = generateState(); | ||
// PKCE code verifier length and alphabet defined in RFC 7636 section 4.1 | ||
const code_verifier = generateRandomString( | ||
96, | ||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_.~" | ||
); | ||
const url = createUrl("https://lichess.org/oauth", { | ||
response_type: "code", | ||
client_id: config.clientId, | ||
code_challenge_method: "S256", | ||
code_challenge: pkceBase64urlEncode( | ||
await pkceCodeChallenge(code_verifier) | ||
), | ||
scope: scope([], config.scope), | ||
redirect_uri: config.redirectUri, | ||
state | ||
}); | ||
return [url, state, code_verifier] as const; | ||
}; | ||
|
||
const getLichessTokens = async (code: string, codeVerifier: string) => { | ||
// Not using createUrl since we need to POST | ||
const request = new Request("https://lichess.org/api/token", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/x-www-form-urlencoded" | ||
}, | ||
body: new URLSearchParams({ | ||
client_id: config.clientId, | ||
grant_type: "authorization_code", | ||
redirect_uri: config.redirectUri, | ||
code_verifier: codeVerifier, | ||
code | ||
}).toString() | ||
}); | ||
const tokens = await handleRequest<{ | ||
access_token: string; | ||
expires_in: number; | ||
}>(request); | ||
|
||
return { | ||
accessToken: tokens.access_token, | ||
accessTokenExpiresIn: tokens.expires_in | ||
}; | ||
}; | ||
|
||
const getLichessUser = async (accessToken: string) => { | ||
const request = new Request("https://lichess.org/api/account", { | ||
headers: authorizationHeaders("bearer", accessToken) | ||
}); | ||
const lichessUser = await handleRequest<LichessUser>(request); | ||
return lichessUser; | ||
}; | ||
|
||
const validateCallback = async (code: string, code_verifier: string) => { | ||
const lichessTokens = await getLichessTokens(code, code_verifier); | ||
const lichessUser = await getLichessUser(lichessTokens.accessToken); | ||
const providerUserId = lichessUser.id; | ||
const lichessUserAuth = await providerUserAuth( | ||
auth, | ||
PROVIDER_ID, | ||
providerUserId | ||
); | ||
return { | ||
...lichessUserAuth, | ||
lichessUser, | ||
lichessTokens | ||
}; | ||
}; | ||
|
||
return { | ||
getAuthorizationUrl, | ||
validateCallback | ||
} as const satisfies OAuthProvider; | ||
}; | ||
|
||
export type LichessUser = { | ||
id: string; | ||
username: string; | ||
}; | ||
|
||
// Base64url-encode as specified in RFC 7636 (OAuth PKCE). | ||
const pkceBase64urlEncode = (arg: string) => { | ||
return encodeBase64(arg) | ||
.split("=")[0] | ||
.replace(/\+/g, "-") | ||
.replace(/\//g, "_"); | ||
}; | ||
|
||
// Generates code_challenge from code_verifier, as specified in RFC 7636. | ||
const pkceCodeChallenge = async (verifier: string) => { | ||
const verifierBuffer = new TextEncoder().encode(verifier); | ||
const challengeBuffer = await crypto.subtle.digest("SHA-256", verifierBuffer); | ||
const challengeArray = Array.from(new Uint8Array(challengeBuffer)); | ||
return String.fromCharCode(...challengeArray); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters