Skip to content

Commit

Permalink
Merge pull request #276 from pilcrowonpaper/next
Browse files Browse the repository at this point in the history
Release v3.3.0
  • Loading branch information
pilcrowonpaper authored Feb 12, 2025
2 parents a00c075 + 1ebf685 commit 3c80113
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 2 deletions.
1 change: 1 addition & 0 deletions .RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add Gitea provider ([#265](https://github.com/pilcrowonpaper/arctic/pull/265)).
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Arctic does not strictly follow semantic versioning. While we aim to only introd
- Epic Games
- Facebook
- Figma
- Gitea
- GitHub
- GitLab
- Google
Expand Down
1 change: 1 addition & 0 deletions docs/malta.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
["Etsy", "/providers/etsy"],
["Facebook", "/providers/facebook"],
["Figma", "/providers/figma"],
["Gitea", "/providers/gitea"],
["GitHub", "/providers/github"],
["GitLab", "/providers/gitlab"],
["Google", "/providers/google"],
Expand Down
101 changes: 101 additions & 0 deletions docs/pages/providers/gitea.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
title: "Gitea"
---

# Gitea

OAuth 2.0 provider for Gitea.

Also see [OAuth 2.0 with PKCE](/guides/oauth2-pkce).

## Initialization

The `baseURL` parameter is the full URL where the Gitea instance is hosted. Use `https://gitea.com` for managed servers. Pass the client secret for confidential clients.

```ts
import * as arctic from "arctic";

const baseURL = "https://gitea.com";
const baseURL = "https://my-app.com/gitea";
const gitea = new arctic.gitea(baseURL, clientId, clientSecret, redirectURI);
const gitea = new arctic.gitea(baseURL, clientId, null, redirectURI);
```

## Create authorization URL

```ts
import * as arctic from "arctic";

const state = arctic.generateState();
const codeVerifier = arctic.generateCodeVerifier();
const scopes = ["read:user", "write:notification"];
const url = gitea.createAuthorizationURL(state, codeVerifier, scopes);
```

## Validate authorization code

`validateAuthorizationCode()` will either return an [`OAuth2Tokens`](/reference/main/OAuth2Tokens), or throw one of [`OAuth2RequestError`](/reference/main/OAuth2RequestError), [`ArcticFetchError`](/reference/main/ArcticFetchError), [`UnexpectedResponseError`](/reference/main/UnexpectedResponseError), or [`UnexpectedErrorResponseBodyError`](/reference/main/UnexpectedErrorResponseBodyError). Gitea returns an access token, the access token expiration, and a refresh token.

```ts
import * as arctic from "arctic";

try {
const tokens = await gitea.validateAuthorizationCode(code, codeVerifier);
const accessToken = tokens.accessToken();
const accessTokenExpiresAt = tokens.accessTokenExpiresAt();
const refreshToken = tokens.refreshToken();
} catch (e) {
if (e instanceof arctic.OAuth2RequestError) {
// Invalid authorization code, credentials, or redirect URI
const code = e.code;
// ...
}
if (e instanceof arctic.ArcticFetchError) {
// Failed to call `fetch()`
const cause = e.cause;
// ...
}
// Parse error
}
```

## Refresh access tokens

Use `refreshAccessToken()` to get a new access token using a refresh token. This method's behavior is identical to `validateAuthorizationCode()`.

```ts
import * as arctic from "arctic";

try {
const tokens = await gitea.refreshAccessToken(refreshToken);
const accessToken = tokens.accessToken();
const accessTokenExpiresAt = tokens.accessTokenExpiresAt();
const refreshToken = tokens.refreshToken();
} catch (e) {
if (e instanceof arctic.OAuth2RequestError) {
// Invalid authorization code, credentials, or redirect URI
}
if (e instanceof arctic.ArcticFetchError) {
// Failed to call `fetch()`
}
// Parse error
}
```

## Get user profile

Add the `read:user` scope and use the [`/user` endpoint](https://gitea.com/api/swagger#/user).

```ts
const scopes = ["read:user"];
const url = gitea.createAuthorizationURL(state, codeVerifier, scopes);
```

```ts
const response = await fetch("https://gitea.com/user", {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
const user = await response.json();
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "arctic",
"type": "module",
"version": "3.2.4",
"version": "3.3.0",
"description": "OAuth 2.0 clients for popular providers",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ export { Etsy } from "./providers/etsy.js";
export { EpicGames } from "./providers/epicgames.js";
export { Facebook } from "./providers/facebook.js";
export { Figma } from "./providers/figma.js";
export { Intuit } from "./providers/intuit.js";
export { Gitea } from "./providers/gitea.js";
export { GitHub } from "./providers/github.js";
export { GitLab } from "./providers/gitlab.js";
export { Google } from "./providers/google.js";
export { Intuit } from "./providers/intuit.js";
export { Kakao } from "./providers/kakao.js";
export { KeyCloak } from "./providers/keycloak.js";
export { Lichess } from "./providers/lichess.js";
Expand Down
45 changes: 45 additions & 0 deletions src/providers/gitea.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { CodeChallengeMethod, OAuth2Client } from "../client.js";
import { joinURIAndPath } from "../request.js";

import type { OAuth2Tokens } from "../oauth2.js";

export class Gitea {
private authorizationEndpoint: string;
private tokenEndpoint: string;

private client: OAuth2Client;

constructor(baseURL: string, clientId: string, clientSecret: string | null, redirectURI: string) {
this.authorizationEndpoint = joinURIAndPath(baseURL, "/login/oauth/authorize");
this.tokenEndpoint = joinURIAndPath(baseURL, "/login/oauth/access_token");
this.client = new OAuth2Client(clientId, clientSecret, redirectURI);
}

public createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL {
const url = this.client.createAuthorizationURLWithPKCE(
this.authorizationEndpoint,
state,
CodeChallengeMethod.S256,
codeVerifier,
scopes
);
return url;
}

public async validateAuthorizationCode(
code: string,
codeVerifier: string
): Promise<OAuth2Tokens> {
const tokens = await this.client.validateAuthorizationCode(
this.tokenEndpoint,
code,
codeVerifier
);
return tokens;
}

public async refreshAccessToken(refreshToken: string): Promise<OAuth2Tokens> {
const tokens = await this.client.refreshAccessToken(this.tokenEndpoint, refreshToken, []);
return tokens;
}
}

0 comments on commit 3c80113

Please sign in to comment.