create with
> wiki-plugin-useraccesstokens@0.1.0 npx > create-wiki-plugin Usage: npm create wiki-plugin <new-plugin-name> e.g. npm create wiki-plugin CoolThing and write requirements in README.
This commit is contained in:
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@ -0,0 +1,10 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
34
.github/workflows/check.yml
vendored
Normal file
34
.github/workflows/check.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
name: Prettier Check
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
prettier:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run Prettier check
|
||||
run: npm run prettier:check
|
||||
|
||||
- name: Annotate Pull Request with Results
|
||||
if: failure()
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '⚠️ Prettier found formatting issues. Please fix them.'
|
||||
})
|
27
.github/workflows/test.yml
vendored
Normal file
27
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
# Releases https://github.com/nodejs/release#release-schedule
|
||||
node-version:
|
||||
- 20.x # Maintenance
|
||||
- 22.x # Active
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm ci
|
||||
- run: npm test
|
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
assets
|
||||
client/useraccesstokens.js
|
||||
client/useraccesstokens.map
|
||||
commons
|
||||
coverage
|
||||
node_modules
|
||||
npm-debug.log
|
||||
meta-client.json
|
||||
recycle
|
||||
status
|
14
.npmignore
Normal file
14
.npmignore
Normal file
@ -0,0 +1,14 @@
|
||||
coverage
|
||||
scripts
|
||||
src
|
||||
test
|
||||
meta-client.json
|
||||
eslint.config.js
|
||||
.github
|
||||
.prettier*
|
||||
.vscode
|
||||
.zed
|
||||
assets
|
||||
commons
|
||||
recycle
|
||||
status
|
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@ -0,0 +1,2 @@
|
||||
client/*.js
|
||||
coverage
|
8
.prettierrc.json
Normal file
8
.prettierrc.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": true,
|
||||
"arrowParens": "avoid",
|
||||
"printWidth": 120
|
||||
}
|
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
|
||||
}
|
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true
|
||||
}
|
7
.zed/settings.json
Normal file
7
.zed/settings.json
Normal file
@ -0,0 +1,7 @@
|
||||
// Folder-specific settings
|
||||
//
|
||||
// For a full list of overridable settings, and general information on folder-specific settings,
|
||||
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
|
||||
{
|
||||
"format_on_save": "on"
|
||||
}
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) <YEAR> <COPYRIGHT HOLDER>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
56
README.md
Normal file
56
README.md
Normal file
@ -0,0 +1,56 @@
|
||||
# Federated Wiki - UserAccessTokens Plugin
|
||||
|
||||
This plugin allows users to create, manage, and revoke User Access Tokens. User Access Tokens, also known elsewhere as "API tokens" or "personal access tokens", offer a way to authenticate users without requiring them to enter their username and password for every request. This allows for interactions with FedWiki through scripts or other applications while maintaining security.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Token generation:
|
||||
- Generate cryptographically secure random tokens (32+ bytes) using `crypto` with a secure hashing algorithm (e.g., bcrypt or Argon2).
|
||||
- Include a token type prefix `fwuat-` to distinguish User Access Tokens from other types of tokens in the system.
|
||||
- Token storage:
|
||||
- Store tokens in the file system, specifically in the site's `status` subdirectory in a file named `user-access-tokens.json`.
|
||||
- Token format:
|
||||
- Each token should be a JSON object with the following structure:
|
||||
```json
|
||||
{
|
||||
"name": "TOKEN_NAME",
|
||||
"user": "USERNAME",
|
||||
"tokenHash": "UNIQUE_TOKEN_STRING",
|
||||
"displayHint": "LAST_FOUR_CHARACTERS_OF_TOKEN",
|
||||
"created": "DATE_STRING_ISO8601",
|
||||
"expires": "DATE_STRING_ISO8601",
|
||||
"lastUsed": "DATE_STRING_ISO8601",
|
||||
"revoked": false, // or true
|
||||
"scopes": ["site:read", "site:write"] // optional, for future use
|
||||
}
|
||||
```
|
||||
- `name`: A human-readable name for the token. This must be unique for the site.
|
||||
- `user`: The username of the user who created the token.
|
||||
- `tokenHash`: A unique, securely generated token string stored in a hashed format using a secure hashing algorithm (e.g., bcrypt or Argon2).
|
||||
- `displayHint`: The last four characters of the token, used for display purposes. Helps users identify tokens without revealing the full token.
|
||||
- `created`: The date and time when the token was created, in ISO 8601 format.
|
||||
- `expires`: The date and time when the token will expire, in ISO 8601 format. If not set, the token does not expire.
|
||||
- `lastUsed`: The date and time when the token was last used, in ISO 8601 format.
|
||||
- `revoked`: A boolean indicating whether the token has been revoked.
|
||||
- `scopes`: An array of strings representing the scopes assigned to the token. If empty, the token has full access. This is a placeholder for future enhancements.
|
||||
- Token management:
|
||||
- Provide UI and API endpoints for creating, listing, revoking, and deleting tokens.
|
||||
- Return the full token only once (upon creation) to the user. Make sure to inform the user to store it securely, as it will not be retrievable later.
|
||||
- Tokens will not work if they are expired, revoked, or deleted.
|
||||
- The user may GET a token or list of tokens, however, the hashed token string should be stripped from the response to prevent accidental exposure.
|
||||
- Token usage:
|
||||
- Tokens should be included in the `Authorization` HTTP header of API requests, using the `Bearer` scheme.
|
||||
- Token validation and security:
|
||||
- Check only tokens with the `fwuat-` prefix.
|
||||
- Tokens should be treated as sensitive information. They should not be logged or exposed in any way.
|
||||
- Implement rate limiting to prevent abuse of the API using tokens. This is necessary because tokens can be used without user interaction.
|
||||
- Tokens should only be transmitted over secure connections (HTTPS) to prevent interception.
|
||||
|
||||
## Build
|
||||
|
||||
`npm install`
|
||||
`npm run build`
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
23
eslint.config.js
Normal file
23
eslint.config.js
Normal file
@ -0,0 +1,23 @@
|
||||
import globals from 'globals'
|
||||
import pluginJs from '@eslint/js'
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
pluginJs.configs.recommended,
|
||||
{
|
||||
rules: {
|
||||
'no-unused-vars': 'warn',
|
||||
},
|
||||
},
|
||||
{ ignores: ['client/*'] },
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
wiki: 'readonly',
|
||||
...globals.browser,
|
||||
...globals.jquery,
|
||||
...globals.mocha,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
1602
package-lock.json
generated
Normal file
1602
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
package.json
Normal file
32
package.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "wiki-plugin-useraccesstokens",
|
||||
"version": "0.1.0",
|
||||
"description": "Add description here.",
|
||||
"keywords": [
|
||||
"wiki",
|
||||
"federated wiki",
|
||||
"plugin"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "npm run clean; npm run test; node --no-warnings scripts/build-client.js",
|
||||
"about": "wiki -p 3010 -d . --security_legacy",
|
||||
"clean": "rm client/useraccesstokens.js client/useraccesstokens.js.map",
|
||||
"clean-about": "rm -r assets commons recycle status",
|
||||
"prettier:format": "prettier --write './**/*.js'",
|
||||
"prettier:check": "prettier --check ./**/*.js",
|
||||
"test": "node --test"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=20.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.29.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"eslint": "^9.29.0",
|
||||
"globals": "^16.2.0",
|
||||
"prettier": "^3.5.3"
|
||||
}
|
||||
}
|
20
pages/about-useraccesstokens-plugin
Normal file
20
pages/about-useraccesstokens-plugin
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"title": "About UserAccessTokens Plugin",
|
||||
"story": [
|
||||
{ "type": "paragraph", "id": "03770af2dea78190", "text": "Describe the plugin here." },
|
||||
{ "type": "useraccesstokens", "id": "76e9b31c71a6a853", "text": "SAMPLE Markup" }
|
||||
],
|
||||
"journal": [
|
||||
{
|
||||
"type": "create",
|
||||
"item": {
|
||||
"title": "About UserAccessTokens Plugin",
|
||||
"story": [
|
||||
{ "type": "paragraph", "id": "03770af2dea78190", "text": "Describe the plugin here." },
|
||||
{ "type": "useraccesstokens", "id": "76e9b31c71a6a853" , "text":"SAMPLE Markup" }
|
||||
]
|
||||
},
|
||||
"date": 1750459617960
|
||||
}
|
||||
]
|
||||
}
|
22
scripts/build-client.js
Normal file
22
scripts/build-client.js
Normal file
@ -0,0 +1,22 @@
|
||||
import * as esbuild from 'esbuild'
|
||||
import fs from 'node:fs/promises'
|
||||
import packJSON from '../package.json' with { type: 'json' }
|
||||
|
||||
const version = packJSON.version
|
||||
const now = new Date()
|
||||
|
||||
let results = await esbuild.build({
|
||||
entryPoints: ['src/client/useraccesstokens.js'],
|
||||
bundle: true,
|
||||
banner: {
|
||||
js: `/* wiki-plugin-useraccesstokens - ${version} - ${now.toUTCString()} */`,
|
||||
},
|
||||
minify: true,
|
||||
sourcemap: true,
|
||||
logLevel: 'info',
|
||||
metafile: true,
|
||||
outfile: 'client/useraccesstokens.js',
|
||||
})
|
||||
|
||||
await fs.writeFile('meta-client.json', JSON.stringify(results.metafile))
|
||||
console.log("\n esbuild metadata written to 'meta-client.json'.")
|
15
scripts/update-authors.cjs
Normal file
15
scripts/update-authors.cjs
Normal file
@ -0,0 +1,15 @@
|
||||
const gitAuthors = require('grunt-git-authors')
|
||||
|
||||
gitAuthors.updatePackageJson({ order: 'date' }, error => {
|
||||
if (error) {
|
||||
console.log('Error: ', error)
|
||||
}
|
||||
})
|
||||
|
||||
gitAuthors.updateAuthors((error, filename) => {
|
||||
if (error) {
|
||||
console.log('Error: ', error)
|
||||
} else {
|
||||
console.log(filename, 'updated')
|
||||
}
|
||||
})
|
26
src/client/useraccesstokens.js
Normal file
26
src/client/useraccesstokens.js
Normal file
@ -0,0 +1,26 @@
|
||||
const expand = text => {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/\*(.+?)\*/g, '<i>$1</i>')
|
||||
}
|
||||
|
||||
const emit = ($item, item) => {
|
||||
return $item.append(`
|
||||
<p style="background-color:#eee;padding:15px;">
|
||||
${expand(item.text)}
|
||||
</p>`)
|
||||
}
|
||||
|
||||
const bind = ($item, item) => {
|
||||
return $item.dblclick(() => {
|
||||
return wiki.textEditor($item, item)
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.plugins.useraccesstokens = { emit, bind }
|
||||
}
|
||||
|
||||
export const useraccesstokens = typeof window == 'undefined' ? { expand } : undefined
|
12
test/test.js
Normal file
12
test/test.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { useraccesstokens } from '../src/client/useraccesstokens.js'
|
||||
import { suite, test } from 'node:test'
|
||||
import assert from 'node:assert'
|
||||
|
||||
suite('useraccesstokens plugin', () => {
|
||||
suite('expand', () => {
|
||||
test('can escape html markup characters', () => {
|
||||
const result = useraccesstokens.expand('try < & >')
|
||||
assert.equal(result, 'try < & >')
|
||||
})
|
||||
})
|
||||
})
|
Reference in New Issue
Block a user