Merge pull request #61 from o-development/fix/v7

Fix/v7
main
jaxoncreed 9 months ago committed by GitHub
commit 375c69d27c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      lerna.json
  2. 4693
      package-lock.json
  3. 2
      packages/cli/package.json
  4. 6
      packages/demo-react/package.json
  5. 2
      packages/ldo/package.json
  6. 6
      packages/solid-react/package.json
  7. 8
      packages/solid-react/test/test-server/configs/components-config/unauthenticatedServer.json
  8. 5
      packages/solid-react/test/test-server/configs/solid-css-seed.json
  9. 19
      packages/solid-react/test/test-server/solidServer.helper.ts
  10. 1
      packages/solid/babel.config.js
  11. 4
      packages/solid/jest.config.js
  12. 13
      packages/solid/package.json
  13. 5
      packages/solid/src/requester/requests/checkRootContainer.ts
  14. 19
      packages/solid/test/Integration.test.ts
  15. 112
      packages/solid/test/authFetch.helper.ts
  16. 43
      packages/solid/test/configs/server-config.json
  17. 6
      packages/solid/test/configs/solid-css-seed.json
  18. 99
      packages/solid/test/solidServer.helper.ts

@ -1,4 +1,4 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "0.0.1-alpha.27"
"version": "0.0.1-alpha.28"
}

4693
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"name": "@ldo/cli",
"version": "0.0.1-alpha.26",
"version": "0.0.1-alpha.28",
"description": "A Command Line Interface for Linked Data Objects",
"main": "./dist/index.js",
"bin": {

@ -1,9 +1,9 @@
{
"name": "@ldo/demo-react",
"version": "0.0.1-alpha.27",
"version": "0.0.1-alpha.28",
"dependencies": {
"@inrupt/solid-client-authn-browser": "^2.0.0",
"@ldo/solid-react": "^0.0.1-alpha.27",
"@ldo/solid-react": "^0.0.1-alpha.28",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.15.0",
@ -37,7 +37,7 @@
},
"devDependencies": {
"@craco/craco": "^7.1.0",
"@ldo/cli": "^0.0.1-alpha.26",
"@ldo/cli": "^0.0.1-alpha.28",
"@types/jsonld": "^1.5.9",
"@types/react": "^18.2.21",
"@types/shexj": "^2.1.4",

@ -1,6 +1,6 @@
{
"name": "@ldo/ldo",
"version": "0.0.1-alpha.26",
"version": "0.0.1-alpha.28",
"description": "",
"main": "dist/index.js",
"scripts": {

@ -1,6 +1,6 @@
{
"name": "@ldo/solid-react",
"version": "0.0.1-alpha.27",
"version": "0.0.1-alpha.28",
"description": "A React library for LDO and Solid",
"main": "dist/index.js",
"scripts": {
@ -39,8 +39,8 @@
"@inrupt/solid-client-authn-browser": "^2.0.0",
"@ldo/dataset": "^0.0.1-alpha.24",
"@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.24",
"@ldo/ldo": "^0.0.1-alpha.26",
"@ldo/solid": "^0.0.1-alpha.26",
"@ldo/ldo": "^0.0.1-alpha.28",
"@ldo/solid": "^0.0.1-alpha.28",
"@ldo/subscribable-dataset": "^0.0.1-alpha.24",
"@rdfjs/data-model": "^1.2.0",
"cross-fetch": "^3.1.6"

@ -1,9 +1,8 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
"import": [
"css:config/app/init/initialize-intro.json",
"css:config/app/main/default.json",
"css:config/app/init/initialize-prefilled-root.json",
"css:config/app/setup/optional.json",
"css:config/app/variables/default.json",
"css:config/http/handler/default.json",
"css:config/http/middleware/default.json",
@ -13,9 +12,9 @@
"css:config/identity/access/public.json",
"css:config/identity/email/default.json",
"css:config/identity/handler/default.json",
"css:config/identity/oidc/default.json",
"css:config/identity/ownership/token.json",
"css:config/identity/pod/static.json",
"css:config/identity/registration/enabled.json",
"css:config/ldp/authentication/dpop-bearer.json",
"css:config/ldp/authorization/webacl.json",
"css:config/ldp/handler/default.json",
@ -24,6 +23,7 @@
"css:config/ldp/modes/default.json",
"css:config/storage/backend/memory.json",
"css:config/storage/key-value/resource-store.json",
"css:config/storage/location/root.json",
"css:config/storage/middleware/default.json",
"css:config/util/auxiliary/acl.json",
"css:config/util/identifiers/suffix.json",

@ -1,8 +1,9 @@
[
{
"podName": "example",
"email": "hello@example.com",
"password": "abc123",
"template": "./template"
"pods": [
{ "name": "example" }
]
}
]

@ -12,28 +12,25 @@ export async function createApp(): Promise<App> {
} as App;
}
const appRunner = new AppRunner();
return appRunner.create(
{
return appRunner.create({
loaderProperties: {
mainModulePath: resolveModulePath(""),
typeChecking: false,
},
path.join(
config: path.join(
__dirname,
"configs",
"components-config",
"unauthenticatedServer.json",
),
{},
{
variableBindings: {},
shorthand: {
port: 3_001,
loggingLevel: "off",
seededPodConfigJson: path.join(
__dirname,
"configs",
"solid-css-seed.json",
),
seedConfig: path.join(__dirname, "configs", "solid-css-seed.json"),
},
);
});
}
export interface ISecretData {

@ -0,0 +1 @@
module.exports = { presets: ["@babel/preset-env"] };

@ -4,4 +4,8 @@ module.exports = {
...sharedConfig,
rootDir: "./",
setupFiles: ["<rootDir>/test/setup-tests.ts"],
transform: {
"^.+\\.(ts|tsx)?$": "ts-jest",
"^.+\\.(js|jsx)$": "babel-jest",
},
};

@ -1,13 +1,13 @@
{
"name": "@ldo/solid",
"version": "0.0.1-alpha.26",
"version": "0.0.1-alpha.28",
"description": "A library for LDO and Solid",
"main": "dist/index.js",
"scripts": {
"example": "ts-node ./example/example.ts",
"build": "tsc --project tsconfig.build.json",
"watch": "tsc --watch",
"test": "jest --coverage",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage",
"test:watch": "jest --watch",
"prepublishOnly": "npm run test && npm run build",
"build:ldo": "ldo build --input src/.shapes --output src/.ldo",
@ -25,12 +25,13 @@
},
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/solid#readme",
"devDependencies": {
"@inrupt/solid-client-authn-core": "^1.17.1",
"@ldo/cli": "^0.0.1-alpha.26",
"@inrupt/solid-client-authn-core": "^2.2.6",
"@ldo/cli": "^0.0.1-alpha.28",
"@rdfjs/data-model": "^1.2.0",
"@rdfjs/types": "^1.0.1",
"@solid/community-server": "^6.0.2",
"@solid/community-server": "^7.1.3",
"@types/jest": "^27.0.3",
"cross-env": "^7.0.3",
"dotenv": "^16.3.1",
"jest-rdf": "^1.8.0",
"ts-jest": "^27.1.2",
@ -41,7 +42,7 @@
},
"dependencies": {
"@ldo/dataset": "^0.0.1-alpha.24",
"@ldo/ldo": "^0.0.1-alpha.26",
"@ldo/ldo": "^0.0.1-alpha.28",
"@ldo/rdf-utils": "^0.0.1-alpha.24",
"cross-fetch": "^3.1.6",
"http-link-header": "^1.1.1"

@ -85,7 +85,10 @@ export async function checkRootContainer(
try {
const fetch = guaranteeFetch(options?.fetch);
// Fetch options to determine the document type
const response = await fetch(uri, { method: "HEAD" });
// Note cache: "no-store": we don't want to depend on cached results because
// web browsers do not cache link headers
// https://github.com/CommunitySolidServer/CommunitySolidServer/issues/1959
const response = await fetch(uri, { method: "HEAD", cache: "no-store" });
const httpErrorResult = HttpErrorResult.checkResponse(uri, response);
if (httpErrorResult) return httpErrorResult;

@ -8,12 +8,7 @@ import type {
UpdateResultError,
} from "../src";
import { changeData, commitData, createSolidLdoDataset } from "../src";
import {
ROOT_CONTAINER,
WEB_ID,
createApp,
getAuthenticatedFetch,
} from "./solidServer.helper";
import { ROOT_CONTAINER, WEB_ID, createApp } from "./solidServer.helper";
import {
namedNode,
quad as createQuad,
@ -46,6 +41,7 @@ import type { NoncompliantPodError } from "../src/requester/results/error/Noncom
import type { GetWacRuleSuccess } from "../src/resource/wac/results/GetWacRuleSuccess";
import type { WacRule } from "../src/resource/wac/WacRule";
import type { GetStorageContainerFromWebIdSuccess } from "../src/requester/results/success/CheckRootContainerSuccess";
import { generateAuthFetch } from "./authFetch.helper";
const TEST_CONTAINER_SLUG = "test_ldo/";
const TEST_CONTAINER_URI =
@ -157,16 +153,25 @@ describe("Integration", () => {
>;
let solidLdoDataset: SolidLdoDataset;
let previousJestId: string | undefined;
let previousNodeEnv: string | undefined;
beforeAll(async () => {
// Remove Jest ID so that community solid server doesn't use the Jest Import
previousJestId = process.env.JEST_WORKER_ID;
previousNodeEnv = process.env.NODE_ENV;
delete process.env.JEST_WORKER_ID;
process.env.NODE_ENV = "other_test";
// Start up the server
app = await createApp();
await app.start();
authFetch = await getAuthenticatedFetch();
authFetch = await generateAuthFetch();
});
afterAll(async () => {
app.stop();
process.env.JEST_WORKER_ID = previousJestId;
process.env.NODE_ENV = previousNodeEnv;
});
beforeEach(async () => {

@ -0,0 +1,112 @@
import type { KeyPair } from "@inrupt/solid-client-authn-core";
import {
buildAuthenticatedFetch,
createDpopHeader,
generateDpopKeyPair,
} from "@inrupt/solid-client-authn-core";
import fetch from "cross-fetch";
const config = {
podName: process.env.USER_NAME || "example",
email: process.env.EMAIL || "hello@example.com",
password: process.env.PASSWORD || "abc123",
};
async function getAuthorization(): Promise<string> {
// First we request the account API controls to find out where we can log in
const indexResponse = await fetch("http://localhost:3001/.account/");
const { controls } = await indexResponse.json();
// And then we log in to the account API
const response = await fetch(controls.password.login, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
email: config.email,
password: config.password,
}),
});
// This authorization value will be used to authenticate in the next step
const result = await response.json();
return result.authorization;
}
async function getSecret(
authorization: string,
): Promise<{ id: string; secret: string; resource: string }> {
// Now that we are logged in, we need to request the updated controls from the server.
// These will now have more values than in the previous example.
const indexResponse = await fetch("http://localhost:3001/.account/", {
headers: { authorization: `CSS-Account-Token ${authorization}` },
});
const { controls } = await indexResponse.json();
// Here we request the server to generate a token on our account
const response = await fetch(controls.account.clientCredentials, {
method: "POST",
headers: {
authorization: `CSS-Account-Token ${authorization}`,
"content-type": "application/json",
},
// The name field will be used when generating the ID of your token.
// The WebID field determines which WebID you will identify as when using the token.
// Only WebIDs linked to your account can be used.
body: JSON.stringify({
name: "my-token",
webId: `http://localhost:3001/${config.podName}/profile/card#me`,
}),
});
// These are the identifier and secret of your token.
// Store the secret somewhere safe as there is no way to request it again from the server!
// The `resource` value can be used to delete the token at a later point in time.
const response2 = await response.json();
return response2;
}
async function getAccessToken(
id: string,
secret: string,
): Promise<{ accessToken: string; dpopKey: KeyPair }> {
try {
// A key pair is needed for encryption.
// This function from `solid-client-authn` generates such a pair for you.
const dpopKey = await generateDpopKeyPair();
// These are the ID and secret generated in the previous step.
// Both the ID and the secret need to be form-encoded.
const authString = `${encodeURIComponent(id)}:${encodeURIComponent(
secret,
)}`;
// This URL can be found by looking at the "token_endpoint" field at
// http://localhost:3001/.well-known/openid-configuration
// if your server is hosted at http://localhost:3000/.
const tokenUrl = "http://localhost:3001/.oidc/token";
const response = await fetch(tokenUrl, {
method: "POST",
headers: {
// The header needs to be in base64 encoding.
authorization: `Basic ${Buffer.from(authString).toString("base64")}`,
"content-type": "application/x-www-form-urlencoded",
dpop: await createDpopHeader(tokenUrl, "POST", dpopKey),
},
body: "grant_type=client_credentials&scope=webid",
});
// This is the Access token that will be used to do an authenticated request to the server.
// The JSON also contains an "expires_in" field in seconds,
// which you can use to know when you need request a new Access token.
const response2 = await response.json();
return { accessToken: response2.access_token, dpopKey };
} catch (err) {
console.error(err);
throw err;
}
}
export async function generateAuthFetch() {
const authorization = await getAuthorization();
const { id, secret } = await getSecret(authorization);
const { accessToken, dpopKey } = await getAccessToken(id, secret);
return await buildAuthenticatedFetch(accessToken, { dpopKey });
}

@ -0,0 +1,43 @@
{
"@context": [
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld"
],
"import": [
"css:config/app/init/static-root.json",
"css:config/app/main/default.json",
"css:config/app/variables/default.json",
"css:config/http/handler/default.json",
"css:config/http/middleware/default.json",
"css:config/http/notifications/all.json",
"css:config/http/server-factory/http.json",
"css:config/http/static/default.json",
"css:config/identity/access/public.json",
"css:config/identity/email/default.json",
"css:config/identity/handler/default.json",
"css:config/identity/oidc/default.json",
"css:config/identity/ownership/token.json",
"css:config/identity/pod/static.json",
"css:config/ldp/authentication/dpop-bearer.json",
"css:config/ldp/authorization/webacl.json",
"css:config/ldp/handler/default.json",
"css:config/ldp/metadata-parser/default.json",
"css:config/ldp/metadata-writer/default.json",
"css:config/ldp/modes/default.json",
"css:config/storage/backend/memory.json",
"css:config/storage/key-value/resource-store.json",
"css:config/storage/location/pod.json",
"css:config/storage/middleware/default.json",
"css:config/util/auxiliary/acl.json",
"css:config/util/identifiers/suffix.json",
"css:config/util/index/default.json",
"css:config/util/logging/winston.json",
"css:config/util/representation-conversion/default.json",
"css:config/util/resource-locker/memory.json",
"css:config/util/variables/default.json"
],
"@graph": [
{
"comment": "A Solid server that stores its resources on disk and uses WAC for authorization."
}
]
}

@ -1,7 +1,9 @@
[
{
"podName": "example",
"email": "hello@example.com",
"password": "abc123"
"password": "abc123",
"pods": [
{ "name": "example" }
]
}
]

@ -1,24 +1,9 @@
// Taken from https://github.com/comunica/comunica/blob/b237be4265c353a62a876187d9e21e3bc05123a3/engines/query-sparql/test/QuerySparql-solid-test.ts#L9
import * as path from "path";
import type { KeyPair } from "@inrupt/solid-client-authn-core";
import {
buildAuthenticatedFetch,
createDpopHeader,
generateDpopKeyPair,
} from "@inrupt/solid-client-authn-core";
import type { App } from "@solid/community-server";
import { AppRunner, resolveModulePath } from "@solid/community-server";
import "jest-rdf";
import fetch from "cross-fetch";
const config = [
{
podName: process.env.USER_NAME || "example",
email: process.env.EMAIL || "hello@example.com",
password: process.env.PASSWORD || "abc123",
},
];
export const SERVER_DOMAIN = process.env.SERVER || "http://localhost:3001/";
export const ROOT_ROUTE = process.env.ROOT_CONTAINER || "";
@ -37,88 +22,18 @@ export async function createApp(): Promise<App> {
} as App;
}
const appRunner = new AppRunner();
return appRunner.create(
{
return appRunner.create({
loaderProperties: {
mainModulePath: resolveModulePath(""),
typeChecking: false,
},
resolveModulePath("config/default.json"),
{},
{
config: resolveModulePath("config/default.json"),
variableBindings: {},
shorthand: {
port: 3_001,
loggingLevel: "off",
seededPodConfigJson: path.join(
__dirname,
"configs",
"solid-css-seed.json",
),
seedConfig: path.join(__dirname, "configs", "solid-css-seed.json"),
},
);
}
export interface ISecretData {
id: string;
secret: string;
}
// From https://communitysolidserver.github.io/CommunitySolidServer/5.x/usage/client-credentials/
export async function getSecret(): Promise<ISecretData> {
const result = await fetch(`${SERVER_DOMAIN}idp/credentials/`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
email: config[0].email,
password: config[0].password,
name: config[0].podName,
}),
});
const json = await result.json();
return json;
}
export interface ITokenData {
accessToken: string;
dpopKey: KeyPair;
}
// From https://communitysolidserver.github.io/CommunitySolidServer/5.x/usage/client-credentials/
export async function refreshToken({
id,
secret,
}: ISecretData): Promise<ITokenData> {
const dpopKey = await generateDpopKeyPair();
const authString = `${encodeURIComponent(id)}:${encodeURIComponent(secret)}`;
const tokenUrl = `${SERVER_DOMAIN}.oidc/token`;
const accessToken = await fetch(tokenUrl, {
method: "POST",
headers: {
// The header needs to be in base64 encoding.
authorization: `Basic ${Buffer.from(authString).toString("base64")}`,
"content-type": "application/x-www-form-urlencoded",
dpop: await createDpopHeader(tokenUrl, "POST", dpopKey),
},
body: "grant_type=client_credentials&scope=webid",
})
.then((res) => res.json())
.then((res) => res.access_token);
return { accessToken, dpopKey };
}
export async function getAuthenticatedFetch() {
// Generate secret
const secret = await getSecret();
if (!secret) throw new Error("No Secret");
// Get token
const token = await refreshToken(secret);
if (!token) throw new Error("No Token");
// Build authenticated fetch
const authFetch = await buildAuthenticatedFetch(fetch, token.accessToken, {
dpopKey: token.dpopKey,
});
return authFetch;
}

Loading…
Cancel
Save