Compare commits
No commits in common. "fc9035148c7eec7383374fc36ea77dc8426056f7" and "80b4028ec73c731cedcff935e804dd65e3b728e0" have entirely different histories.
fc9035148c
...
80b4028ec7
3 changed files with 95 additions and 89 deletions
|
@ -1,16 +1,11 @@
|
||||||
import { BaseApi } from "./base-api.ts";
|
import { BaseApi } from "./base-api.ts";
|
||||||
|
|
||||||
export type Oauth2ApplicationCreateParams = {
|
|
||||||
name: string,
|
|
||||||
redirect_uris: string[],
|
|
||||||
confidential_client?: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Oauth2Application = {
|
export type Oauth2Application = {
|
||||||
id: number,
|
id: number,
|
||||||
name: string,
|
name: string,
|
||||||
redirect_uris: string[],
|
redirect_uris: string[],
|
||||||
client_id: string,
|
client_id: string,
|
||||||
|
client_secret: string,
|
||||||
confidential_client: boolean,
|
confidential_client: boolean,
|
||||||
/**
|
/**
|
||||||
* @format date-time
|
* @format date-time
|
||||||
|
@ -18,10 +13,6 @@ export type Oauth2Application = {
|
||||||
created: string,
|
created: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Oauth2ApplicationResponse = {
|
|
||||||
client_secret: string,
|
|
||||||
} & Oauth2Application
|
|
||||||
|
|
||||||
export class OAuth2Api extends BaseApi {
|
export class OAuth2Api extends BaseApi {
|
||||||
private token: string;
|
private token: string;
|
||||||
|
|
||||||
|
@ -30,8 +21,17 @@ export class OAuth2Api extends BaseApi {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createOauth2Application(params: Oauth2ApplicationCreateParams): Promise<Oauth2ApplicationResponse> {
|
// Method to update token if needed later
|
||||||
return await this.request<Oauth2ApplicationResponse>(`/api/v1/user/applications/oauth2`, {
|
updateToken(token: string): void {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOauth2Application(params: {
|
||||||
|
name: string,
|
||||||
|
redirect_uris: string[],
|
||||||
|
confidential_client?: boolean,
|
||||||
|
}): Promise<Oauth2Application | undefined> {
|
||||||
|
return await this.request<Oauth2Application>(`/api/v1/user/applications/oauth2`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `Bearer ${this.token}`,
|
"Authorization": `Bearer ${this.token}`,
|
||||||
|
@ -41,7 +41,7 @@ export class OAuth2Api extends BaseApi {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOauth2Applications(): Promise<Oauth2Application[]> {
|
async getOauth2Applications(): Promise<Oauth2Application[] | undefined> {
|
||||||
return await this.request<Oauth2Application[]>(`/api/v1/user/applications/oauth2`, {
|
return await this.request<Oauth2Application[]>(`/api/v1/user/applications/oauth2`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -65,8 +65,12 @@ export class OAuth2Api extends BaseApi {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOauth2Application(id: number, params: Oauth2ApplicationCreateParams): Promise<Oauth2ApplicationResponse> {
|
async updateOauth2Application(id: number, params: {
|
||||||
return await this.request<Oauth2ApplicationResponse>(`/api/v1/user/applications/oauth2/${id}`, {
|
name: string,
|
||||||
|
redirect_uris: string[],
|
||||||
|
confidential_client?: boolean,
|
||||||
|
}): Promise<Oauth2Application | undefined> {
|
||||||
|
return await this.request<Oauth2Application>(`/api/v1/user/applications/oauth2/${id}`, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `Bearer ${this.token}`,
|
"Authorization": `Bearer ${this.token}`,
|
||||||
|
|
126
app.ts
126
app.ts
|
@ -14,46 +14,46 @@ const CREDENTIALS_FILE = `${homedir()}/.oauth2cli-forgejo`;
|
||||||
async function main() {
|
async function main() {
|
||||||
const tokenApi = new TokenApi(URL_BASE, TOKEN_NAME);
|
const tokenApi = new TokenApi(URL_BASE, TOKEN_NAME);
|
||||||
const credentialManager = new CredentialManager(CREDENTIALS_FILE, TOKEN_NAME);
|
const credentialManager = new CredentialManager(CREDENTIALS_FILE, TOKEN_NAME);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await new Command()
|
await new Command()
|
||||||
.name("forgejo-oauth2cli")
|
.name("forgejo-oauth2cli")
|
||||||
.version("0.1.0")
|
.version("0.1.0")
|
||||||
.description("Interactive client credentials generator for Forgejo.")
|
.description("Interactive client credentials generator for Forgejo.")
|
||||||
.command("login", "Authenticate with Forgejo interactively")
|
.command("login", "Authenticate with Forgejo interactively")
|
||||||
.action(async () => {
|
.action(async() => {
|
||||||
const username = await prompt("Enter your username: ");
|
const username = await prompt("Enter your username: ");
|
||||||
if (!username) {
|
if (!username) {
|
||||||
console.error("Username is required.");
|
console.error("Username is required.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const secret = await Secret.prompt("Enter your secret: ");
|
const secret = await Secret.prompt("Enter your secret: ");
|
||||||
if (!secret) {
|
if (!secret) {
|
||||||
console.error("Secret is required.");
|
console.error("Secret is required.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for existing tokens
|
// Check for existing tokens
|
||||||
const tokens = await tokenApi.listTokens(username, secret);
|
const tokens = await tokenApi.listTokens(username, secret);
|
||||||
if (!tokens) {
|
if (!tokens) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete existing token if found
|
// Delete existing token if found
|
||||||
const existingToken = tokens.find(t => t.name === TOKEN_NAME);
|
const existingToken = tokens.find(t => t.name === TOKEN_NAME);
|
||||||
if (existingToken) {
|
if (existingToken) {
|
||||||
console.log("Existing token found, replacing it...");
|
console.log("Existing token found, replacing it...");
|
||||||
await tokenApi.deleteToken(username, secret, existingToken.id);
|
await tokenApi.deleteToken(username, secret, existingToken.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new token
|
// Create new token
|
||||||
console.log("Creating new token...");
|
console.log("Creating new token...");
|
||||||
const token = await tokenApi.createToken(username, secret);
|
const token = await tokenApi.createToken(username, secret);
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store credentials
|
// Store credentials
|
||||||
await credentialManager.storeCredentials(username, token.sha1);
|
await credentialManager.storeCredentials(username, token.sha1);
|
||||||
console.log("Login successful! Credentials stored securely.");
|
console.log("Login successful! Credentials stored securely.");
|
||||||
|
@ -65,176 +65,154 @@ async function main() {
|
||||||
console.error("Username is required.");
|
console.error("Username is required.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const secret = await Secret.prompt("Enter your secret: ");
|
const secret = await Secret.prompt("Enter your secret: ");
|
||||||
if (!secret) {
|
if (!secret) {
|
||||||
console.error("Secret is required.");
|
console.error("Secret is required.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find and delete token
|
// Find and delete token
|
||||||
const tokens = await tokenApi.listTokens(username, secret);
|
const tokens = await tokenApi.listTokens(username, secret);
|
||||||
if (!tokens) {
|
if (!tokens) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingToken = tokens.find(t => t.name === TOKEN_NAME);
|
const existingToken = tokens.find(t => t.name === TOKEN_NAME);
|
||||||
if (existingToken) {
|
if (existingToken) {
|
||||||
await tokenApi.deleteToken(username, secret, existingToken.id);
|
await tokenApi.deleteToken(username, secret, existingToken.id);
|
||||||
console.log("Token deleted from Forgejo.");
|
console.log("Token deleted from Forgejo.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear stored credentials
|
// Clear stored credentials
|
||||||
await credentialManager.clearCredentials(username);
|
await credentialManager.clearCredentials(username);
|
||||||
console.log("Logout successful! Credentials removed.");
|
console.log("Logout successful! Credentials removed.");
|
||||||
})
|
})
|
||||||
.command("list, ls", "List all OAuth2 applications")
|
.command("list, ls", "List all OAuth2 applications")
|
||||||
.option("--pretty, -p", "Pretty print the output")
|
.action(async () => {
|
||||||
.action(async (opt) => {
|
|
||||||
const credentials = await credentialManager.getCredentials();
|
const credentials = await credentialManager.getCredentials();
|
||||||
if (!credentials) return;
|
if (!credentials) return;
|
||||||
|
|
||||||
// Create OAuth2Api with token
|
// Create OAuth2Api with token
|
||||||
const oauth2Api = new OAuth2Api(URL_BASE, credentials.token);
|
const oauth2Api = new OAuth2Api(URL_BASE, credentials.token);
|
||||||
const apps = await oauth2Api.getOauth2Applications();
|
const apps = await oauth2Api.getOauth2Applications();
|
||||||
if (!apps) return;
|
if (!apps) return;
|
||||||
|
|
||||||
if (apps.length === 0) {
|
if (apps.length === 0) {
|
||||||
console.log("No OAuth2 applications found.");
|
console.log("No OAuth2 applications found.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (opt.pretty) {
|
|
||||||
console.log("OAuth2 applications:");
|
console.log("OAuth2 applications:");
|
||||||
console.log(apps.map(prettyOauth2Application).join("\n\n"));
|
console.log(apps.map(prettyOauth2Application).join("\n\n"));
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const maxNameLength = Math.max(...apps.map(app => app.name.length), 6);
|
|
||||||
console.log(apps.map(app => [
|
|
||||||
app.id.toString().padEnd(5),
|
|
||||||
app.confidential_client ? "c" : "o",
|
|
||||||
app.name.padStart(maxNameLength),
|
|
||||||
app.client_id,
|
|
||||||
app.redirect_uris.join(", "),
|
|
||||||
].join(" ").trim()).join("\n"));
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.command("create", "Create new OAuth2 application")
|
.command("create", "Create new OAuth2 application")
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
const credentials = await credentialManager.getCredentials();
|
const credentials = await credentialManager.getCredentials();
|
||||||
if (!credentials) {
|
if (!credentials) return;
|
||||||
console.error("Credentials not found. Please login first.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create OAuth2Api with token
|
// Create OAuth2Api with token
|
||||||
const oauth2Api = new OAuth2Api(URL_BASE, credentials.token);
|
const oauth2Api = new OAuth2Api(URL_BASE, credentials.token);
|
||||||
|
|
||||||
const name = await prompt("Enter the name of the application: ");
|
const name = await prompt("Enter the name of the application: ");
|
||||||
if (!name) {
|
if (!name) {
|
||||||
console.error("Name is required.");
|
console.error("Name is required.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const redirectUris = await prompt("Enter the redirect URIs (comma separated): ");
|
const redirectUris = await prompt("Enter the redirect URIs (comma separated): ");
|
||||||
if (!redirectUris) {
|
if (!redirectUris) {
|
||||||
console.error("Redirect URIs are required.");
|
console.error("Redirect URIs are required.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = await oauth2Api.createOauth2Application({
|
const app = await oauth2Api.createOauth2Application({
|
||||||
name,
|
name,
|
||||||
redirect_uris: redirectUris.split(",").map(uri => uri.trim()),
|
redirect_uris: redirectUris.split(",").map(uri => uri.trim()),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!app) return;
|
if (!app) return;
|
||||||
|
|
||||||
console.log("OAuth2 application created successfully!");
|
console.log("OAuth2 application created successfully!");
|
||||||
console.log(prettyOauth2Application(app));
|
console.log(prettyOauth2Application(app));
|
||||||
|
|
||||||
// Save secret keys
|
// Save secret keys
|
||||||
const path = await prompt("Enter the path to save the OAuth2 secret keys: ");
|
const path = await prompt("Enter the path to save the OAuth2 secret keys: ");
|
||||||
if (path) {
|
if (path) {
|
||||||
await saveSecretKeys(path, app.client_id, app.client_secret);
|
await saveSecretKeys(path, app.client_id, app.client_secret);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.command("remove, rm", "Remove OAuth2 application")
|
.command("delete", "Delete OAuth2 application")
|
||||||
.arguments("<id:number>")
|
.action(async () => {
|
||||||
.action(async (_opt, appId) => {
|
|
||||||
const credentials = await credentialManager.getCredentials();
|
const credentials = await credentialManager.getCredentials();
|
||||||
if (!credentials) {
|
if (!credentials) return;
|
||||||
console.error("Credentials not found. Please login first.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create OAuth2Api with token
|
// Create OAuth2Api with token
|
||||||
const oauth2Api = new OAuth2Api(URL_BASE, credentials.token);
|
const oauth2Api = new OAuth2Api(URL_BASE, credentials.token);
|
||||||
|
|
||||||
|
const id = await prompt("Enter the ID of the application to delete: ");
|
||||||
|
if (!id) {
|
||||||
|
console.error("ID is required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const appId = parseInt(id);
|
||||||
|
if (isNaN(appId)) {
|
||||||
|
console.error("ID must be a number.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await oauth2Api.deleteOauth2Application(appId);
|
await oauth2Api.deleteOauth2Application(appId);
|
||||||
console.log("OAuth2 application deleted successfully!");
|
console.log("OAuth2 application deleted successfully!");
|
||||||
})
|
})
|
||||||
.command("update", "Update OAuth2 application")
|
.command("update", "Update OAuth2 application")
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
const credentials = await credentialManager.getCredentials();
|
const credentials = await credentialManager.getCredentials();
|
||||||
if (!credentials) {
|
if (!credentials) return;
|
||||||
console.error("Credentials not found. Please login first.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create OAuth2Api with token
|
// Create OAuth2Api with token
|
||||||
const oauth2Api = new OAuth2Api(URL_BASE, credentials.token);
|
const oauth2Api = new OAuth2Api(URL_BASE, credentials.token);
|
||||||
|
|
||||||
const id = await prompt("Enter the ID of the application to update: ");
|
const id = await prompt("Enter the ID of the application to update: ");
|
||||||
if (!id) {
|
if (!id) {
|
||||||
console.error("ID is required.");
|
console.error("ID is required.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const appId = parseInt(id);
|
const appId = parseInt(id);
|
||||||
if (isNaN(appId)) {
|
if (isNaN(appId)) {
|
||||||
console.error("ID must be a number.");
|
console.error("ID must be a number.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = await prompt("Enter the new name of the application: ");
|
const name = await prompt("Enter the new name of the application: ");
|
||||||
if (!name) {
|
if (!name) {
|
||||||
console.error("Name is required.");
|
console.error("Name is required.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const redirectUris = await prompt("Enter the new redirect URIs (comma separated): ");
|
const redirectUris = await prompt("Enter the new redirect URIs (comma separated): ");
|
||||||
if (!redirectUris) {
|
if (!redirectUris) {
|
||||||
console.error("Redirect URIs are required.");
|
console.error("Redirect URIs are required.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = await oauth2Api.updateOauth2Application(appId, {
|
const app = await oauth2Api.updateOauth2Application(appId, {
|
||||||
name,
|
name,
|
||||||
redirect_uris: redirectUris.split(",").map(uri => uri.trim()),
|
redirect_uris: redirectUris.split(",").map(uri => uri.trim()),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!app) return;
|
if (!app) return;
|
||||||
|
|
||||||
console.log("OAuth2 application updated successfully!");
|
console.log("OAuth2 application updated successfully!");
|
||||||
console.log(prettyOauth2Application(app));
|
console.log(prettyOauth2Application(app));
|
||||||
|
|
||||||
// Save secret keys
|
// Save secret keys
|
||||||
const path = await prompt("Enter the path to save the OAuth2 secret keys: ");
|
const path = await prompt("Enter the path to save the OAuth2 secret keys: ");
|
||||||
if (path) {
|
if (path) {
|
||||||
await saveSecretKeys(path, app.client_id, app.client_secret);
|
await saveSecretKeys(path, app.client_id, app.client_secret);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.command("fetch-config", "Fetch OpenID configuration")
|
|
||||||
.action(async () => {
|
|
||||||
const response = await fetch(`${URL_BASE}/.well-known/openid-configuration`);
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error("Failed to fetch OpenID configuration:", response.statusText);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = await response.json();
|
|
||||||
console.log("OpenID Configuration:", JSON.stringify(config, null, 2));
|
|
||||||
})
|
|
||||||
.parse(Deno.args);
|
.parse(Deno.args);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
|
|
24
types.ts
Normal file
24
types.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
export const URL_BASE = "https://git.monoid.top";
|
||||||
|
export const TOKEN_NAME = "oauth2cli";
|
||||||
|
export const CREDENTIALS_FILE = `${Deno.env.get("HOME") || ""}/.oauth2cli-forgejo`;
|
||||||
|
|
||||||
|
export type Token = {
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
scopes: string[],
|
||||||
|
sha1: string,
|
||||||
|
token_last_eight: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Oauth2Application = {
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
redirect_uris: string[],
|
||||||
|
client_id: string,
|
||||||
|
client_secret: string,
|
||||||
|
confidential_client: boolean,
|
||||||
|
/**
|
||||||
|
* @format date-time
|
||||||
|
*/
|
||||||
|
created: string,
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue