From b28e7d5069597f6baa20c75b97324fcbee96d2c0 Mon Sep 17 00:00:00 2001 From: "Ruben D." Date: Mon, 6 Jan 2025 13:27:51 +0100 Subject: [PATCH] Added suppport for Inrupt application registration flow --- src/authentication/CreateFetch.ts | 2 +- src/authentication/TokenCreationCSS.ts | 34 +++++++++-- src/index.ts | 4 +- src/shell/commands/auth.ts | 81 +++++++++++++++++++++++++- src/utils/configoptions.ts | 5 +- 5 files changed, 113 insertions(+), 13 deletions(-) diff --git a/src/authentication/CreateFetch.ts b/src/authentication/CreateFetch.ts index bd63d53..8c65df3 100644 --- a/src/authentication/CreateFetch.ts +++ b/src/authentication/CreateFetch.ts @@ -29,7 +29,7 @@ export interface IClientCredentialsTokenAuthOptions { logger?: Logger, } -export interface IClientCredentialsTokenGenerationOptions { +export interface ICSSClientCredentialsTokenGenerationOptions { name: string, email: string, password: string, diff --git a/src/authentication/TokenCreationCSS.ts b/src/authentication/TokenCreationCSS.ts index 8932567..aca509f 100644 --- a/src/authentication/TokenCreationCSS.ts +++ b/src/authentication/TokenCreationCSS.ts @@ -6,7 +6,7 @@ import { createDpopHeader, generateDpopKeyPair } from '@inrupt/solid-client-auth const express = require('express') -export interface IClientCredentialsTokenGenerationOptions { +export interface ICSSClientCredentialsTokenGenerationOptions { name: string, email: string, password: string, @@ -14,6 +14,20 @@ export interface IClientCredentialsTokenGenerationOptions { webId?: string, } +export interface IInruptClientCredentialsTokenGenerationOptions { + id: string, + secret: string, + idp: string, + webId?: string, +} + + +export type InruptToken = { + id: string, + secret: string, + idp: string, +} + export type CSSToken = { id: string, secret: string, @@ -25,11 +39,11 @@ export type CSSToken = { import crossfetch from 'cross-fetch'; -export async function generateCSSToken(options: IClientCredentialsTokenGenerationOptions) { +export async function generateCSSToken(options: ICSSClientCredentialsTokenGenerationOptions) { return generateCSSTokenVersion7(options) } -export async function generateCSSTokenVersion7(options: IClientCredentialsTokenGenerationOptions) { +export async function generateCSSTokenVersion7(options: ICSSClientCredentialsTokenGenerationOptions) { if (!options.idp) throw new BashlibError(BashlibErrorMessage.noIDPOption) if (!options.webId) throw new BashlibError(BashlibErrorMessage.noWebIDOption) @@ -81,14 +95,12 @@ export async function generateCSSTokenVersion7(options: IClientCredentialsTokenG name: options.name, email: options.email, idp: options.idp, - - } as CSSToken return token; } -export async function generateCSSTokenVersion6(options: IClientCredentialsTokenGenerationOptions) { +export async function generateCSSTokenVersion6(options: ICSSClientCredentialsTokenGenerationOptions) { if (!options.idp) throw new BashlibError(BashlibErrorMessage.noIDPOption) @@ -121,3 +133,13 @@ export async function generateCSSTokenVersion6(options: IClientCredentialsTokenG return token as CSSToken; } + + +export function generateInruptToken(options: IInruptClientCredentialsTokenGenerationOptions): InruptToken { + + return { + id: options.id, + secret: options.secret, + idp: options.idp + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index e4aeeaf..1bd2e0b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,7 +11,7 @@ import touch, {ICommandOptionsTouch} from "./commands/solid-touch" import createSolidPods, {IAccountData} from "./commands/solid-pod-create" import { listPermissions, changePermissions, deletePermissions, ICommandOptionsPermissions, IPermissionOperation, IPermissionListing, Record } from './commands/solid-perms' import { authenticateWithTokenFromJavascript } from "./authentication/AuthenticationToken" -import { generateCSSToken, IClientCredentialsTokenGenerationOptions, CSSToken } from "./authentication/TokenCreationCSS" +import { generateCSSToken, ICSSClientCredentialsTokenGenerationOptions, CSSToken } from "./authentication/TokenCreationCSS" import { FileInfo, ResourceInfo } from './utils/util'; // General Solid functionality @@ -30,4 +30,4 @@ export type { Logger } from './logger'; export type { ICommandOptionsCopy, ICommandOptionsList, ICommandOptionsRemove, ICommandOptionsMove, ICommandOptionsFind, ICommandOptionsQuery, ICommandOptionsPermissions, ICommandOptionsMakeDirectory, ICommandOptionsTouch } // Type exports -export type { IAccountData, IClientCredentialsTokenGenerationOptions, CSSToken, IPermissionOperation, FileInfo, ResourceInfo, SessionInfo, IPermissionListing, Record } \ No newline at end of file +export type { IAccountData, ICSSClientCredentialsTokenGenerationOptions, CSSToken, IPermissionOperation, FileInfo, ResourceInfo, SessionInfo, IPermissionListing, Record } \ No newline at end of file diff --git a/src/shell/commands/auth.ts b/src/shell/commands/auth.ts index 1bf788b..a4a20da 100644 --- a/src/shell/commands/auth.ts +++ b/src/shell/commands/auth.ts @@ -6,7 +6,7 @@ import inquirer from 'inquirer'; import cliSelect from "cli-select" import chalk from 'chalk'; -import { generateCSSToken } from '../../authentication/TokenCreationCSS'; +import { generateCSSToken, generateInruptToken } from '../../authentication/TokenCreationCSS'; import { getWebIDIdentityProvider, writeErrorString } from '../../utils/util'; import { generateDpopKeyPair } from '@inrupt/solid-client-authn-core'; import { requestAccessToken } from '../../authentication/AuthenticationToken'; @@ -91,7 +91,7 @@ export default class AuthCommand extends SolidCommand { }) authcommand - .command('create-token') + .command('create-token-css') .description('create authentication token (only for WebIDs hosted on a Community Solid Server v4.0.0 and up).') .option('-b, --base-url ', 'URL of your CSS server') .option('-n, --name ', 'Token name') @@ -108,6 +108,23 @@ export default class AuthCommand extends SolidCommand { if (this.mayExit) process.exit(0) }) + + authcommand + .command('create-token-ess') + .description('Store application id and secret for authentication token generation (register bashlib here: https://login.inrupt.com/registration.html).') + .option('-b, --base-url ', 'URL of your Inrupt server (default is https://login.inrupt.com/)') + .option('-i, --id ', 'application registration id') + .option('-s, --secret ', 'application registration secret') + .option('-v, --verbose', 'Log actions') + .action(async (options) => { + try { + await createAuthenticationTokenInrupt(options) + } catch (e) { + writeErrorString('Could not create authentication token', e, options) + if (this.mayExit) process.exit(1) + } + if (this.mayExit) process.exit(0) + }) return program } } @@ -299,6 +316,66 @@ async function createAuthenticationTokenCSS(options: any) { } } +async function createAuthenticationTokenInrupt(options: any){ + options.name = options.name || "Solid-cli token" + let questions = [] + + let currentWebID = getConfigCurrentWebID(); + let createTokenForCurrentWebID = false; + if (currentWebID) { + console.log(`Do you want to create an authentication token for ${currentWebID}? [Y/n] `); + createTokenForCurrentWebID = await new Promise((resolve, reject) => { + process.stdin.setRawMode(true); + process.stdin.resume(); + process.stdin.on('data', (chk) => { + if (chk.toString('utf8') === "n") { + resolve(false); + } else { + resolve(true); + } + }); + }); + } + + if (createTokenForCurrentWebID) { + let session = getConfigCurrentSession() + let token = getConfigCurrentToken() + let webId = getConfigCurrentWebID() + if(webId) options.webId = webId; + if (!options.baseUrl && webId) { + options.baseUrl = session?.idp || token?.idp || await getWebIDIdentityProvider(webId) + } + } + + if (!options.baseUrl) questions.push({ type: 'input', name: 'baseUrl', message: 'Solid server URI', default: "https://login.inrupt.com/"}) + if (!options.email) questions.push({ type: 'input', name: 'id', message: 'id'}) + if (!options.password) questions.push({ type: 'input', name: 'secret', message: 'secret'}) + + if (questions.length) { + let answers = await inquirer.prompt(questions) + options = { ...options, ...answers } + } + options.idp = options.baseUrl; + + + try { + let token = await generateInruptToken(options); + + // Get token WebID by creating an access token (a bit wastefull but no other option sadly) + if (!token.id || !token.secret) throw new Error('Could not create valid authentication token.') + + const dpopKey = await generateDpopKeyPair(); + let { accessToken, expirationDate, webId } = await requestAccessToken(token.id, token.secret, dpopKey, options); + + if (!webId) throw new Error('Could not create valid authentication token.') + setConfigToken(webId, token) + console.log(`Successfully created new token ${options.name}`) + } catch (e) { + console.error(`Could not create token: ${(e as Error).message}`) + console.error(`Please make sure the filled in email and password values are correct!`) + } +} + async function setAuthenticationOption_backup(options: any) { let webId = options.webid diff --git a/src/utils/configoptions.ts b/src/utils/configoptions.ts index 5d86a00..d3947cb 100644 --- a/src/utils/configoptions.ts +++ b/src/utils/configoptions.ts @@ -16,11 +16,12 @@ export type IAuthInfoEntry = { } export type ITokenEntry = { - name: string, - email: string, + name?: string, + email?: string, idp: string, webId: string, id: string, + secret?: string, } export type ISessionEntry = {