@julr/sesame 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +130 -0
  3. package/build/authorize_controller-CUdEDNEi.js +136 -0
  4. package/build/client_info_controller-DeIVcW8B.js +18 -0
  5. package/build/client_service-B9fD3ZGe.js +53 -0
  6. package/build/commands/commands.json +1 -0
  7. package/build/commands/main.d.ts +4 -0
  8. package/build/commands/main.js +36 -0
  9. package/build/commands/sesame_purge.d.ts +21 -0
  10. package/build/commands/sesame_purge.js +28 -0
  11. package/build/configure.d.ts +2 -0
  12. package/build/configure.js +16 -0
  13. package/build/consent_controller-DFfx7qVs.js +87 -0
  14. package/build/decorate-2_8Ex77k.js +15 -0
  15. package/build/index.d.ts +14 -0
  16. package/build/index.js +27 -0
  17. package/build/introspect_controller-BzwfaUUE.js +63 -0
  18. package/build/main-kn40V-hF.js +2 -0
  19. package/build/metadata_controller-BSRRElQX.js +51 -0
  20. package/build/oauth_access_token-BpG8sq-c.js +18 -0
  21. package/build/oauth_client-eh0e5ql-.js +24 -0
  22. package/build/oauth_error-BQPqV-MV.js +78 -0
  23. package/build/providers/sesame_provider.d.ts +21 -0
  24. package/build/providers/sesame_provider.js +19 -0
  25. package/build/register_controller-BA7uQAgt.js +139 -0
  26. package/build/revoke_controller-CNIgNKH3.js +50 -0
  27. package/build/rolldown-runtime-BASaM9lw.js +12 -0
  28. package/build/routes-D6QCu0Pz.js +43 -0
  29. package/build/sesame_manager-B4tO2PLO.js +116 -0
  30. package/build/src/controllers/authorize_controller.d.ts +53 -0
  31. package/build/src/controllers/client_info_controller.d.ts +22 -0
  32. package/build/src/controllers/consent_controller.d.ts +27 -0
  33. package/build/src/controllers/introspect_controller.d.ts +28 -0
  34. package/build/src/controllers/metadata_controller.d.ts +64 -0
  35. package/build/src/controllers/register_controller.d.ts +91 -0
  36. package/build/src/controllers/revoke_controller.d.ts +16 -0
  37. package/build/src/controllers/token_controller.d.ts +24 -0
  38. package/build/src/decorators.d.ts +10 -0
  39. package/build/src/define_config.d.ts +16 -0
  40. package/build/src/grants/authorization_code_grant.d.ts +27 -0
  41. package/build/src/grants/refresh_token_grant.d.ts +27 -0
  42. package/build/src/guard/guard.d.ts +30 -0
  43. package/build/src/guard/main.d.ts +20 -0
  44. package/build/src/guard/main.js +17 -0
  45. package/build/src/guard/types.d.ts +46 -0
  46. package/build/src/guard/user_provider.d.ts +14 -0
  47. package/build/src/models/oauth_access_token.d.ts +23 -0
  48. package/build/src/models/oauth_authorization_code.d.ts +30 -0
  49. package/build/src/models/oauth_client.d.ts +33 -0
  50. package/build/src/models/oauth_consent.d.ts +22 -0
  51. package/build/src/models/oauth_refresh_token.d.ts +29 -0
  52. package/build/src/oauth_error.d.ts +359 -0
  53. package/build/src/routes.d.ts +28 -0
  54. package/build/src/rules.d.ts +12 -0
  55. package/build/src/services/client_service.d.ts +67 -0
  56. package/build/src/services/token_service.d.ts +42 -0
  57. package/build/src/sesame_manager.d.ts +66 -0
  58. package/build/src/types.d.ts +141 -0
  59. package/build/stubs/config/sesame.stub +30 -0
  60. package/build/stubs/main.d.ts +5 -0
  61. package/build/stubs/migrations/create_oauth_access_tokens_table.stub +25 -0
  62. package/build/stubs/migrations/create_oauth_authorization_codes_table.stub +27 -0
  63. package/build/stubs/migrations/create_oauth_clients_table.stub +31 -0
  64. package/build/stubs/migrations/create_oauth_consents_table.stub +24 -0
  65. package/build/stubs/migrations/create_oauth_refresh_tokens_table.stub +26 -0
  66. package/build/token_controller-C9wh813f.js +172 -0
  67. package/build/token_service-Czz9v5GI.js +30 -0
  68. package/build/user_provider-B3rXEUT3.js +150 -0
  69. package/package.json +144 -0
package/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ # The MIT License
2
+
3
+ Copyright (c) 2023
4
+
5
+ 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:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ 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.
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # AdonisJS package starter kit
2
+
3
+ > [!note]
4
+ > This starter kit targets **AdonisJS v7**
5
+
6
+ > A boilerplate for creating AdonisJS packages
7
+
8
+ This repo provides you with a starting point for creating AdonisJS packages. Of course, you can create a package from scratch with your folder structure and workflow. However, using this starter kit can speed up the process, as you have fewer decisions to make.
9
+
10
+ ## Setup
11
+
12
+ - Clone the repo on your computer, or use `giget` to download this repo without the Git history.
13
+ ```sh
14
+ npx giget@latest gh:adonisjs/pkg-starter-kit
15
+ ```
16
+ - Install dependencies.
17
+ - Update the `package.json` file and define the `name`, `description`, `keywords`, and `author` properties.
18
+ - The repo is configured with an MIT license. Feel free to change that if you are not publishing under the MIT license.
19
+
20
+ ## Folder structure
21
+
22
+ The starter kit mimics the folder structure of the official packages. Feel free to rename files and folders as per your requirements.
23
+
24
+ ```
25
+ ├── providers
26
+ ├── src
27
+ ├── bin
28
+ ├── stubs
29
+ ├── configure.ts
30
+ ├── index.ts
31
+ ├── LICENSE.md
32
+ ├── package.json
33
+ ├── README.md
34
+ ├── tsconfig.json
35
+ ├── tsnode.esm.js
36
+ ```
37
+
38
+ - The `configure.ts` file exports the `configure` hook to configure the package using the `node ace configure` command.
39
+ - The `index.ts` file is the main entry point of the package.
40
+ - The `tsnode.esm.js` file runs TypeScript code using TS-Node + SWC. Please read the code comment in this file to learn more.
41
+ - The `bin` directory contains the entry point file to run Japa tests.
42
+ - Learn more about [the `providers` directory](./providers/README.md).
43
+ - Learn more about [the `src` directory](./src/README.md).
44
+ - Learn more about [the `stubs` directory](./stubs/README.md).
45
+
46
+ ### File system naming convention
47
+
48
+ We use `snake_case` naming conventions for the file system. The rule is enforced using ESLint. However, turn off the rule and use your preferred naming conventions.
49
+
50
+ ## Peer dependencies
51
+
52
+ The starter kit has a peer dependency on `@adonisjs/core@6`. Since you are creating a package for AdonisJS, you must make it against a specific version of the framework core.
53
+
54
+ If your package needs Lucid to be functional, you may install `@adonisjs/lucid` as a development dependency and add it to the list of `peerDependencies`.
55
+
56
+ As a rule of thumb, packages installed in the user application should be part of the `peerDependencies` of your package and not the main dependency.
57
+
58
+ For example, if you install `@adonisjs/core` as a main dependency, then essentially, you are importing a separate copy of `@adonisjs/core` and not sharing the one from the user application. Here is a great article explaining [peer dependencies](https://blog.bitsrc.io/understanding-peer-dependencies-in-javascript-dbdb4ab5a7be).
59
+
60
+ ## Published files
61
+
62
+ Instead of publishing your repo's source code to npm, you must cherry-pick files and folders to publish only the required files.
63
+
64
+ The cherry-picking uses the `files` property inside the `package.json` file. By default, we publish the following files and folders.
65
+
66
+ ```json
67
+ {
68
+ "files": [
69
+ "build/src",
70
+ "build/providers",
71
+ "build/stubs",
72
+ "build/index.d.ts",
73
+ "build/index.js",
74
+ "build/configure.d.ts",
75
+ "build/configure.js"
76
+ ]
77
+ }
78
+ ```
79
+
80
+ If you create additional folders or files, mention them inside the `files` array.
81
+
82
+ ## Exports
83
+
84
+ [Node.js Subpath exports](https://nodejs.org/api/packages.html#subpath-exports) allows you to define the exports of your package regardless of the folder structure. This starter kit defines the following exports.
85
+
86
+ ```json
87
+ {
88
+ "exports": {
89
+ ".": "./build/index.js",
90
+ "./types": "./build/src/types.js"
91
+ }
92
+ }
93
+ ```
94
+
95
+ - The dot `.` export is the main export.
96
+ - The `./types` exports all the types defined inside the `./build/src/types.js` file (the compiled output).
97
+
98
+ Feel free to change the exports as per your requirements.
99
+
100
+ ## Testing
101
+
102
+ We configure the [Japa test runner](https://japa.dev/) with this starter kit. Japa is used in AdonisJS applications as well. Just run one of the following commands to execute tests.
103
+
104
+ - `npm run test`: This command will first lint the code using ESlint and then run tests and report the test coverage using [c8](https://github.com/bcoe/c8).
105
+ - `npm run quick:test`: Runs only the tests without linting or coverage reporting.
106
+
107
+ The starter kit also has a Github workflow file to run tests using Github Actions. The tests are executed against `Node.js 20.x` and `Node.js 21.x` versions on both Linux and Windows. Feel free to edit the workflow file in the `.github/workflows` directory.
108
+
109
+ ## TypeScript workflow
110
+
111
+ - The starter kit uses [tsc](https://www.typescriptlang.org/docs/handbook/compiler-options.html) for compiling the TypeScript to JavaScript when publishing the package.
112
+ - [TS-Node](https://typestrong.org/ts-node/) and [SWC](https://swc.rs/) are used to run tests without compiling the source code.
113
+ - The `tsconfig.json` file is extended from [`@adonisjs/tsconfig`](https://github.com/adonisjs/tooling-config/tree/main/packages/typescript-config) and uses the `NodeNext` module system. Meaning the packages are written using ES modules.
114
+ - You can perform type checking without compiling the source code using the `npm run type check` script.
115
+
116
+ Feel free to explore the `tsconfig.json` file for all the configured options.
117
+
118
+ ## ESLint and Prettier setup
119
+
120
+ The starter kit configures ESLint and Prettier
121
+ using our [shared config](https://github.com/adonisjs/tooling-config/tree/main/packages).
122
+ ESLint configuration is stored within the `eslint.config.js` file.
123
+ Prettier configuration is stored within the `package.json` file.
124
+ Feel free to change the configuration, use custom plugins, or remove both tools altogether.
125
+
126
+ ## Using Stale bot
127
+
128
+ The [Stale bot](https://github.com/apps/stale) is a Github application that automatically marks issues and PRs as stale and closes after a specific duration of inactivity.
129
+
130
+ Feel free to delete the `.github/stale.yml` and `.github/lock.yml` files if you decide not to use the Stale bot.
@@ -0,0 +1,136 @@
1
+ import "./decorate-2_8Ex77k.js";
2
+ import "./oauth_access_token-BpG8sq-c.js";
3
+ import { i as OAuthAuthorizationCode, r as OAuthConsent, t as SesameManager } from "./sesame_manager-B4tO2PLO.js";
4
+ import { a as E_INVALID_REQUEST, n as E_INVALID_CLIENT, u as E_UNSUPPORTED_RESPONSE_TYPE } from "./oauth_error-BQPqV-MV.js";
5
+ import { t as OAuthClient } from "./oauth_client-eh0e5ql-.js";
6
+ import { t as TokenService } from "./token_service-Czz9v5GI.js";
7
+ import { t as ClientService } from "./client_service-B9fD3ZGe.js";
8
+ import { DateTime } from "luxon";
9
+ import vine from "@vinejs/vine";
10
+ var AuthorizeController = class AuthorizeController {
11
+ static validator = vine.create({
12
+ client_id: vine.string(),
13
+ response_type: vine.string(),
14
+ redirect_uri: vine.string(),
15
+ scope: vine.string().optional(),
16
+ state: vine.string().optional(),
17
+ code_challenge: vine.string().optional(),
18
+ code_challenge_method: vine.string().optional()
19
+ });
20
+ #getAuthorizationSession(ctx) {
21
+ const session = ctx.session;
22
+ if (!session || typeof session.put !== "function") throw new E_INVALID_REQUEST("Session middleware is required for the browser authorization flow");
23
+ return session;
24
+ }
25
+ #redirectWithError(ctx, manager, redirectUri, error, description, state) {
26
+ const url = new URL(redirectUri);
27
+ url.searchParams.set("error", error);
28
+ url.searchParams.set("error_description", description);
29
+ if (state) url.searchParams.set("state", state);
30
+ url.searchParams.set("iss", manager.config.issuer);
31
+ return ctx.response.redirect().toPath(url.toString());
32
+ }
33
+ #buildAuthorizationRequestParams(ctx, manager, options) {
34
+ const rawRequestToken = new TokenService(manager).generateOpaqueToken();
35
+ const session = this.#getAuthorizationSession(ctx);
36
+ session.put("sesame.authToken", rawRequestToken);
37
+ session.put("sesame.authRequest", {
38
+ clientId: options.clientId,
39
+ redirectUri: options.redirectUri,
40
+ scopes: options.scopes,
41
+ state: options.state ?? null,
42
+ codeChallenge: options.codeChallenge ?? null,
43
+ codeChallengeMethod: options.codeChallengeMethod ?? null
44
+ });
45
+ const params = new URLSearchParams();
46
+ params.set("auth_token", rawRequestToken);
47
+ return params;
48
+ }
49
+ #copyAuthorizeDisplayParams(params, query) {
50
+ for (const [key, value] of Object.entries(query)) {
51
+ if (key === "auth_token") continue;
52
+ if (value != null) params.set(key, String(value));
53
+ }
54
+ }
55
+ #resolvePageUrl(page, ctx, params) {
56
+ if (typeof page === "function") return page(ctx, params);
57
+ return `${page}?${params.toString()}`;
58
+ }
59
+ async #issueAuthorizationCode(ctx, manager, options) {
60
+ const tokenService = new TokenService(manager);
61
+ const raw = tokenService.generateOpaqueToken();
62
+ const hashed = tokenService.hashToken(raw);
63
+ const ttl = manager.parseTtl(manager.config.authorizationCodeTtl);
64
+ await OAuthAuthorizationCode.create({
65
+ id: crypto.randomUUID(),
66
+ code: hashed,
67
+ clientId: options.client.clientId,
68
+ userId: options.userId,
69
+ scopes: options.scopes,
70
+ redirectUri: options.redirectUri,
71
+ codeChallenge: options.codeChallenge ?? null,
72
+ codeChallengeMethod: options.codeChallengeMethod ?? null,
73
+ expiresAt: DateTime.now().plus({ seconds: ttl })
74
+ });
75
+ const url = new URL(options.redirectUri);
76
+ url.searchParams.set("code", raw);
77
+ if (options.state) url.searchParams.set("state", options.state);
78
+ url.searchParams.set("iss", manager.config.issuer);
79
+ return ctx.response.redirect().toPath(url.toString());
80
+ }
81
+ async handle(ctx) {
82
+ const manager = await ctx.containerResolver.make(SesameManager);
83
+ const clientService = new ClientService();
84
+ const [error, query] = await AuthorizeController.validator.tryValidate(ctx.request.qs());
85
+ if (error) throw new E_INVALID_REQUEST("Invalid authorization request parameters");
86
+ if (query.response_type !== "code") throw new E_UNSUPPORTED_RESPONSE_TYPE("Only \"code\" is supported");
87
+ const client = await OAuthClient.query().where("clientId", query.client_id).first();
88
+ if (!client) throw new E_INVALID_CLIENT("Client not found");
89
+ if (client.isDisabled) throw new E_INVALID_CLIENT("Client is disabled");
90
+ if (!client.redirectUris.includes(query.redirect_uri)) throw new E_INVALID_REQUEST("Invalid redirect_uri");
91
+ if (!client.grantTypes.includes("authorization_code")) return this.#redirectWithError(ctx, manager, query.redirect_uri, "unauthorized_client", "Client is not allowed to use the authorization_code grant", query.state);
92
+ const requestedScopes = query.scope ? query.scope.split(" ") : manager.config.defaultScopes;
93
+ const invalidScopes = manager.validateScopes(requestedScopes);
94
+ if (invalidScopes.length > 0) return this.#redirectWithError(ctx, manager, query.redirect_uri, "invalid_scope", `Invalid scopes: ${invalidScopes.join(", ")}`, query.state);
95
+ try {
96
+ clientService.validateClientScopes(requestedScopes, client.scopes);
97
+ } catch (error) {
98
+ return this.#redirectWithError(ctx, manager, query.redirect_uri, "invalid_scope", error.message, query.state);
99
+ }
100
+ if (!query.code_challenge) return this.#redirectWithError(ctx, manager, query.redirect_uri, "invalid_request", "PKCE code_challenge is required", query.state);
101
+ if (query.code_challenge_method !== "S256") return this.#redirectWithError(ctx, manager, query.redirect_uri, "invalid_request", "Only S256 code_challenge_method is supported", query.state);
102
+ await ctx.auth.check();
103
+ const user = ctx.auth.user;
104
+ if (!user) {
105
+ const params = new URLSearchParams();
106
+ this.#copyAuthorizeDisplayParams(params, ctx.request.qs());
107
+ const loginPage = this.#resolvePageUrl(manager.config.loginPage, ctx, params);
108
+ return ctx.response.redirect().toPath(loginPage);
109
+ }
110
+ const existingConsent = await OAuthConsent.query().where("clientId", client.clientId).where("userId", String(user.id)).first();
111
+ if (existingConsent) {
112
+ const consentedSet = new Set(existingConsent.scopes);
113
+ if (requestedScopes.every((s) => consentedSet.has(s))) return this.#issueAuthorizationCode(ctx, manager, {
114
+ client,
115
+ userId: String(user.id),
116
+ scopes: requestedScopes,
117
+ redirectUri: query.redirect_uri,
118
+ codeChallenge: query.code_challenge,
119
+ codeChallengeMethod: query.code_challenge_method,
120
+ state: query.state
121
+ });
122
+ }
123
+ const params = this.#buildAuthorizationRequestParams(ctx, manager, {
124
+ clientId: client.clientId,
125
+ redirectUri: query.redirect_uri,
126
+ scopes: requestedScopes,
127
+ state: query.state,
128
+ codeChallenge: query.code_challenge,
129
+ codeChallengeMethod: query.code_challenge_method
130
+ });
131
+ this.#copyAuthorizeDisplayParams(params, ctx.request.qs());
132
+ const consentPage = this.#resolvePageUrl(manager.config.consentPage, ctx, params);
133
+ return ctx.response.redirect().toPath(consentPage);
134
+ }
135
+ };
136
+ export { AuthorizeController as default };
@@ -0,0 +1,18 @@
1
+ import "./decorate-2_8Ex77k.js";
2
+ import { a as E_INVALID_REQUEST, n as E_INVALID_CLIENT } from "./oauth_error-BQPqV-MV.js";
3
+ import { t as OAuthClient } from "./oauth_client-eh0e5ql-.js";
4
+ import vine from "@vinejs/vine";
5
+ var ClientInfoController = class ClientInfoController {
6
+ static validator = vine.create({ client_id: vine.string() });
7
+ async handle(ctx) {
8
+ const [error, query] = await ClientInfoController.validator.tryValidate(ctx.request.qs());
9
+ if (error) throw new E_INVALID_REQUEST("Missing client_id");
10
+ const client = await OAuthClient.query().where("clientId", query.client_id).first();
11
+ if (!client) throw new E_INVALID_CLIENT("Client not found");
12
+ return {
13
+ client_id: client.clientId,
14
+ client_name: client.name
15
+ };
16
+ }
17
+ };
18
+ export { ClientInfoController as default };
@@ -0,0 +1,53 @@
1
+ import { o as E_INVALID_SCOPE } from "./oauth_error-BQPqV-MV.js";
2
+ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
3
+ var ClientService = class {
4
+ parseBasicAuth(header) {
5
+ if (!header.startsWith("Basic ")) return null;
6
+ try {
7
+ const decoded = Buffer.from(header.slice(6), "base64").toString("utf-8");
8
+ const colonIndex = decoded.indexOf(":");
9
+ if (colonIndex === -1) return null;
10
+ return {
11
+ clientId: decodeURIComponent(decoded.slice(0, colonIndex)),
12
+ clientSecret: decodeURIComponent(decoded.slice(colonIndex + 1))
13
+ };
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+ extractCredentials(options) {
19
+ if (options.authorizationHeader) {
20
+ const basic = this.parseBasicAuth(options.authorizationHeader);
21
+ if (basic) return basic;
22
+ }
23
+ if (options.bodyClientId) return {
24
+ clientId: options.bodyClientId,
25
+ clientSecret: options.bodyClientSecret
26
+ };
27
+ return null;
28
+ }
29
+ validateClientScopes(requestedScopes, clientScopes) {
30
+ if (clientScopes.length === 0 && requestedScopes.length > 0) throw new E_INVALID_SCOPE(`Scope not allowed: ${requestedScopes.join(", ")}`);
31
+ const allowedSet = new Set(clientScopes);
32
+ const invalid = requestedScopes.filter((s) => !allowedSet.has(s));
33
+ if (invalid.length > 0) throw new E_INVALID_SCOPE(`Scope not allowed: ${invalid.join(", ")}`);
34
+ }
35
+ hashSecret(secret) {
36
+ return createHash("sha256").update(secret).digest("base64url");
37
+ }
38
+ verifySecret(secret, storedHash) {
39
+ const hash = this.hashSecret(secret);
40
+ try {
41
+ return timingSafeEqual(Buffer.from(hash), Buffer.from(storedHash));
42
+ } catch {
43
+ return false;
44
+ }
45
+ }
46
+ generateClientId() {
47
+ return randomBytes(16).toString("hex");
48
+ }
49
+ generateClientSecret() {
50
+ return randomBytes(32).toString("base64url");
51
+ }
52
+ };
53
+ export { ClientService as t };
@@ -0,0 +1 @@
1
+ {"commands":[{"commandName":"sesame:purge","description":"Purge revoked and/or expired tokens and authorization codes","help":"","namespace":"sesame","aliases":[],"flags":[{"name":"revoked","flagName":"revoked","required":false,"type":"boolean","description":"Only purge revoked tokens and authorization codes"},{"name":"expired","flagName":"expired","required":false,"type":"boolean","description":"Only purge expired tokens and authorization codes"},{"name":"hours","flagName":"hours","required":false,"type":"number","description":"Number of hours to retain expired tokens (default: 168 = 7 days)","default":168}],"args":[],"options":{"startApp":true},"filePath":"sesame_purge.js"}],"version":1}
@@ -0,0 +1,4 @@
1
+ import { CommandMetaData, Command } from '@adonisjs/ace/types';
2
+
3
+ export function getMetaData(): Promise<CommandMetaData[]>
4
+ export function getCommand(metaData: CommandMetaData): Promise<Command | null>
@@ -0,0 +1,36 @@
1
+ import { readFile } from 'node:fs/promises'
2
+
3
+ /**
4
+ * In-memory cache of commands after they have been loaded
5
+ */
6
+ let commandsMetaData
7
+
8
+ /**
9
+ * Reads the commands from the "./commands.json" file. Since, the commands.json
10
+ * file is generated automatically, we do not have to validate its contents
11
+ */
12
+ export async function getMetaData() {
13
+ if (commandsMetaData) {
14
+ return commandsMetaData
15
+ }
16
+
17
+ const commandsIndex = await readFile(new URL('./commands.json', import.meta.url), 'utf-8')
18
+ commandsMetaData = JSON.parse(commandsIndex).commands
19
+
20
+ return commandsMetaData
21
+ }
22
+
23
+ /**
24
+ * Imports the command by lookingup its path from the commands
25
+ * metadata
26
+ */
27
+ export async function getCommand(metaData) {
28
+ const commands = await getMetaData()
29
+ const command = commands.find(({ commandName }) => metaData.commandName === commandName)
30
+ if (!command) {
31
+ return null
32
+ }
33
+
34
+ const { default: commandConstructor } = await import(new URL(command.filePath, import.meta.url).href)
35
+ return commandConstructor
36
+ }
@@ -0,0 +1,21 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import type { CommandOptions } from '@adonisjs/core/types/ace';
3
+ /**
4
+ * Purge revoked and/or expired OAuth tokens and authorization codes.
5
+ *
6
+ * By default, purges both revoked and expired records. Use `--revoked`
7
+ * or `--expired` to target only one category. Expired tokens are
8
+ * retained for a configurable period (default 168h / 7 days) to
9
+ * allow for debugging and audit trails.
10
+ *
11
+ * @see https://datatracker.ietf.org/doc/html/rfc6749
12
+ */
13
+ export default class SesamePurge extends BaseCommand {
14
+ static commandName: string;
15
+ static description: string;
16
+ static options: CommandOptions;
17
+ revoked: boolean;
18
+ expired: boolean;
19
+ hours: number;
20
+ run(): Promise<void>;
21
+ }
@@ -0,0 +1,28 @@
1
+ import { t as __decorate } from "../decorate-2_8Ex77k.js";
2
+ import "../oauth_access_token-BpG8sq-c.js";
3
+ import { t as SesameManager } from "../sesame_manager-B4tO2PLO.js";
4
+ import { BaseCommand, flags } from "@adonisjs/core/ace";
5
+ var SesamePurge = class extends BaseCommand {
6
+ static commandName = "sesame:purge";
7
+ static description = "Purge revoked and/or expired tokens and authorization codes";
8
+ static options = { startApp: true };
9
+ async run() {
10
+ const result = await (await this.app.container.make(SesameManager)).purgeTokens({
11
+ revokedOnly: this.revoked,
12
+ expiredOnly: this.expired,
13
+ retentionHours: this.hours
14
+ });
15
+ const total = result.accessTokens + result.refreshTokens + result.authorizationCodes;
16
+ if (result.accessTokens > 0) this.logger.info(` Access tokens: ${result.accessTokens}`);
17
+ if (result.refreshTokens > 0) this.logger.info(` Refresh tokens: ${result.refreshTokens}`);
18
+ if (result.authorizationCodes > 0) this.logger.info(` Authorization codes: ${result.authorizationCodes}`);
19
+ this.logger.success(`Purged ${total} record(s).`);
20
+ }
21
+ };
22
+ __decorate([flags.boolean({ description: "Only purge revoked tokens and authorization codes" })], SesamePurge.prototype, "revoked", void 0);
23
+ __decorate([flags.boolean({ description: "Only purge expired tokens and authorization codes" })], SesamePurge.prototype, "expired", void 0);
24
+ __decorate([flags.number({
25
+ description: "Number of hours to retain expired tokens (default: 168 = 7 days)",
26
+ default: 168
27
+ })], SesamePurge.prototype, "hours", void 0);
28
+ export { SesamePurge as default };
@@ -0,0 +1,2 @@
1
+ import type Configure from '@adonisjs/core/commands/configure';
2
+ export declare function configure(command: Configure): Promise<void>;
@@ -0,0 +1,16 @@
1
+ import { t as stubsRoot } from "./main-kn40V-hF.js";
2
+ async function configure(command) {
3
+ const codemods = await command.createCodemods();
4
+ await codemods.makeUsingStub(stubsRoot, "config/sesame.stub", {});
5
+ for (const stub of [
6
+ "migrations/create_oauth_clients_table.stub",
7
+ "migrations/create_oauth_authorization_codes_table.stub",
8
+ "migrations/create_oauth_access_tokens_table.stub",
9
+ "migrations/create_oauth_refresh_tokens_table.stub",
10
+ "migrations/create_oauth_consents_table.stub"
11
+ ]) await codemods.makeUsingStub(stubsRoot, stub, {});
12
+ await codemods.updateRcFile((rcFile) => {
13
+ rcFile.addProvider("@julr/sesame/sesame_provider").addCommand("@julr/sesame/commands");
14
+ });
15
+ }
16
+ export { configure };
@@ -0,0 +1,87 @@
1
+ import "./decorate-2_8Ex77k.js";
2
+ import "./oauth_access_token-BpG8sq-c.js";
3
+ import { i as OAuthAuthorizationCode, r as OAuthConsent, t as SesameManager } from "./sesame_manager-B4tO2PLO.js";
4
+ import { a as E_INVALID_REQUEST, i as E_INVALID_GRANT, n as E_INVALID_CLIENT } from "./oauth_error-BQPqV-MV.js";
5
+ import { t as OAuthClient } from "./oauth_client-eh0e5ql-.js";
6
+ import { t as TokenService } from "./token_service-Czz9v5GI.js";
7
+ import { DateTime } from "luxon";
8
+ import vine from "@vinejs/vine";
9
+ var ConsentController = class ConsentController {
10
+ static validator = vine.create({ auth_token: vine.string() });
11
+ #getAuthorizationSession(ctx) {
12
+ const session = ctx.session;
13
+ if (!session || typeof session.pull !== "function") throw new E_INVALID_REQUEST("Session middleware is required for the browser authorization flow");
14
+ return session;
15
+ }
16
+ async #issueAuthorizationCode(ctx, manager, options) {
17
+ const tokenService = new TokenService(manager);
18
+ const raw = tokenService.generateOpaqueToken();
19
+ const hashed = tokenService.hashToken(raw);
20
+ const ttl = manager.parseTtl(manager.config.authorizationCodeTtl);
21
+ await OAuthAuthorizationCode.create({
22
+ id: crypto.randomUUID(),
23
+ code: hashed,
24
+ clientId: options.client.clientId,
25
+ userId: options.userId,
26
+ scopes: options.scopes,
27
+ redirectUri: options.redirectUri,
28
+ codeChallenge: options.codeChallenge ?? null,
29
+ codeChallengeMethod: options.codeChallengeMethod ?? null,
30
+ expiresAt: DateTime.now().plus({ seconds: ttl })
31
+ });
32
+ const url = new URL(options.redirectUri);
33
+ url.searchParams.set("code", raw);
34
+ if (options.state) url.searchParams.set("state", options.state);
35
+ url.searchParams.set("iss", manager.config.issuer);
36
+ return ctx.response.redirect().toPath(url.toString());
37
+ }
38
+ async handle(ctx) {
39
+ const manager = await ctx.containerResolver.make(SesameManager);
40
+ const session = this.#getAuthorizationSession(ctx);
41
+ await ctx.auth.check();
42
+ const user = ctx.auth.user;
43
+ if (!user) throw new E_INVALID_REQUEST("User must be authenticated");
44
+ const [error, body] = await ConsentController.validator.tryValidate(ctx.request.body());
45
+ if (error) throw new E_INVALID_REQUEST("Missing required parameter: auth_token");
46
+ const accept = ctx.request.body().accept;
47
+ const expectedAuthToken = session.pull("sesame.authToken");
48
+ const authorizationRequest = session.pull("sesame.authRequest");
49
+ if (!expectedAuthToken || expectedAuthToken !== body.auth_token) {
50
+ session.forget(["sesame.authToken", "sesame.authRequest"]);
51
+ throw new E_INVALID_GRANT("Authorization request token mismatch");
52
+ }
53
+ if (!authorizationRequest) throw new E_INVALID_GRANT("Authorization request not found");
54
+ const client = await OAuthClient.query().where("clientId", authorizationRequest.clientId).first();
55
+ if (!client) throw new E_INVALID_CLIENT("Client not found");
56
+ if (client.isDisabled) throw new E_INVALID_CLIENT("Client is disabled");
57
+ if (!client.redirectUris.includes(authorizationRequest.redirectUri)) throw new E_INVALID_REQUEST("Invalid redirect_uri");
58
+ if (!accept) {
59
+ const url = new URL(authorizationRequest.redirectUri);
60
+ url.searchParams.set("error", "access_denied");
61
+ url.searchParams.set("error_description", "The user denied the authorization request");
62
+ if (authorizationRequest.state) url.searchParams.set("state", authorizationRequest.state);
63
+ url.searchParams.set("iss", manager.config.issuer);
64
+ return ctx.response.redirect().toPath(url.toString());
65
+ }
66
+ const existingConsent = await OAuthConsent.query().where("clientId", client.clientId).where("userId", String(user.id)).first();
67
+ if (existingConsent) {
68
+ existingConsent.scopes = [...new Set([...existingConsent.scopes, ...authorizationRequest.scopes])];
69
+ await existingConsent.save();
70
+ } else await OAuthConsent.create({
71
+ id: crypto.randomUUID(),
72
+ clientId: client.clientId,
73
+ userId: String(user.id),
74
+ scopes: authorizationRequest.scopes
75
+ });
76
+ return this.#issueAuthorizationCode(ctx, manager, {
77
+ client,
78
+ userId: String(user.id),
79
+ scopes: authorizationRequest.scopes,
80
+ redirectUri: authorizationRequest.redirectUri,
81
+ codeChallenge: authorizationRequest.codeChallenge ?? void 0,
82
+ codeChallengeMethod: authorizationRequest.codeChallengeMethod ?? void 0,
83
+ state: authorizationRequest.state ?? void 0
84
+ });
85
+ }
86
+ };
87
+ export { ConsentController as default };
@@ -0,0 +1,15 @@
1
+ import { column } from "@adonisjs/lucid/orm";
2
+ function json(options) {
3
+ return column({
4
+ ...options,
5
+ prepare: (value) => value == null ? null : JSON.stringify(value),
6
+ consume: (value) => value == null ? null : typeof value === "string" ? JSON.parse(value) : value
7
+ });
8
+ }
9
+ function __decorate(decorators, target, key, desc) {
10
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
11
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
12
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
13
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
14
+ }
15
+ export { json as n, __decorate as t };
@@ -0,0 +1,14 @@
1
+ export { configure } from './configure.ts';
2
+ export { stubsRoot } from './stubs/main.ts';
3
+ export { defineConfig } from './src/define_config.ts';
4
+ export { SesameManager } from './src/sesame_manager.ts';
5
+ export { OAuthError, E_INVALID_REQUEST, E_INVALID_CLIENT, E_INVALID_GRANT, E_INVALID_SCOPE, E_INVALID_TOKEN, E_UNSUPPORTED_GRANT_TYPE, E_UNSUPPORTED_RESPONSE_TYPE, E_ACCESS_DENIED, E_INVALID_CLIENT_METADATA, E_SERVER_ERROR, } from './src/oauth_error.ts';
6
+ export { OAuthClient } from './src/models/oauth_client.ts';
7
+ export { OAuthAccessToken } from './src/models/oauth_access_token.ts';
8
+ export { OAuthRefreshToken } from './src/models/oauth_refresh_token.ts';
9
+ export { OAuthAuthorizationCode } from './src/models/oauth_authorization_code.ts';
10
+ export { OAuthConsent } from './src/models/oauth_consent.ts';
11
+ export { OAuthGuard } from './src/guard/guard.ts';
12
+ export { OAuthLucidUserProvider } from './src/guard/user_provider.ts';
13
+ export { oauthGuard, oauthUserProvider } from './src/guard/main.ts';
14
+ export { registerRoutes, registerProtectedResource } from './src/routes.ts';
package/build/index.js ADDED
@@ -0,0 +1,27 @@
1
+ import { t as stubsRoot } from "./main-kn40V-hF.js";
2
+ import { configure } from "./configure.js";
3
+ import "./decorate-2_8Ex77k.js";
4
+ import { t as OAuthAccessToken } from "./oauth_access_token-BpG8sq-c.js";
5
+ import { a as OAuthRefreshToken, i as OAuthAuthorizationCode, r as OAuthConsent, t as SesameManager } from "./sesame_manager-B4tO2PLO.js";
6
+ import { a as E_INVALID_REQUEST, c as E_SERVER_ERROR, d as OAuthError, i as E_INVALID_GRANT, l as E_UNSUPPORTED_GRANT_TYPE, n as E_INVALID_CLIENT, o as E_INVALID_SCOPE, r as E_INVALID_CLIENT_METADATA, s as E_INVALID_TOKEN, t as E_ACCESS_DENIED, u as E_UNSUPPORTED_RESPONSE_TYPE } from "./oauth_error-BQPqV-MV.js";
7
+ import { t as OAuthClient } from "./oauth_client-eh0e5ql-.js";
8
+ import "./token_service-Czz9v5GI.js";
9
+ import { n as OAuthGuard, t as OAuthLucidUserProvider } from "./user_provider-B3rXEUT3.js";
10
+ import { oauthGuard, oauthUserProvider } from "./src/guard/main.js";
11
+ import { n as registerRoutes, t as registerProtectedResource } from "./routes-D6QCu0Pz.js";
12
+ function defineConfig(config) {
13
+ return {
14
+ issuer: config.issuer,
15
+ scopes: config.scopes ?? {},
16
+ defaultScopes: config.defaultScopes ?? [],
17
+ grantTypes: config.grantTypes ?? ["authorization_code", "refresh_token"],
18
+ accessTokenTtl: config.accessTokenTtl ?? "1h",
19
+ refreshTokenTtl: config.refreshTokenTtl ?? "30d",
20
+ authorizationCodeTtl: config.authorizationCodeTtl ?? "10m",
21
+ loginPage: config.loginPage,
22
+ consentPage: config.consentPage,
23
+ allowDynamicRegistration: config.allowDynamicRegistration ?? false,
24
+ allowPublicRegistration: config.allowPublicRegistration ?? false
25
+ };
26
+ }
27
+ export { E_ACCESS_DENIED, E_INVALID_CLIENT, E_INVALID_CLIENT_METADATA, E_INVALID_GRANT, E_INVALID_REQUEST, E_INVALID_SCOPE, E_INVALID_TOKEN, E_SERVER_ERROR, E_UNSUPPORTED_GRANT_TYPE, E_UNSUPPORTED_RESPONSE_TYPE, OAuthAccessToken, OAuthAuthorizationCode, OAuthClient, OAuthConsent, OAuthError, OAuthGuard, OAuthLucidUserProvider, OAuthRefreshToken, SesameManager, configure, defineConfig, oauthGuard, oauthUserProvider, registerProtectedResource, registerRoutes, stubsRoot };