From 2c7db403cc076e8a3265968d784306903905c9f1 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 17 Jul 2023 12:16:20 +0530 Subject: [PATCH 01/37] refactor: for v6 Breaking change: User defined config must be wrapped inside the defineConfig method --- .adonisrc.json | 16 - .bin/test.js | 7 - .github/COMMIT_CONVENTION.md | 70 -- .github/CONTRIBUTING.md | 46 -- .github/ISSUE_TEMPLATE/bug_report.md | 29 - .github/ISSUE_TEMPLATE/feature_request.md | 28 - .github/PULL_REQUEST_TEMPLATE.md | 28 - .github/labels.json | 170 +++++ .github/stale.yml | 4 +- .github/workflows/checks.yml | 14 + .github/workflows/test.yml | 22 - .husky/commit-msg | 7 +- LICENSE.md | 2 +- adonis-typings/ally.ts | 692 ------------------ adonis-typings/container.ts | 15 - adonis-typings/context.ts | 15 - adonis-typings/index.ts | 12 - bin/japaTypes.ts | 7 - bin/test.ts | 14 +- examples/app.ts | 55 +- examples/config/ally.ts | 51 +- examples/config/app.ts | 10 - examples/discord.ts | 8 +- examples/facebook.ts | 8 +- examples/github.ts | 10 +- examples/google.ts | 8 +- examples/linkedin.ts | 8 +- examples/spotify.ts | 17 +- examples/twitter.ts | 8 +- index.ts | 19 + instructions.ts | 237 ------ package.json | 258 +++---- providers/AllyProvider.ts | 49 -- providers/ally_provider.ts | 22 + src/Ally/index.ts | 47 -- src/AllyManager/index.ts | 179 ----- src/Exceptions/index.ts | 27 - src/RedirectRequest/index.ts | 64 -- .../index.ts => abstract_drivers/oauth1.ts} | 61 +- .../index.ts => abstract_drivers/oauth2.ts} | 59 +- src/ally_manager.ts | 52 ++ src/bindings/http_context.ts | 29 + src/bindings/types.ts | 25 + src/{Config/index.ts => defaults/config.ts} | 0 src/define_config.ts | 56 ++ .../Discord/index.ts => drivers/discord.ts} | 17 +- .../Facebook/index.ts => drivers/facebook.ts} | 17 +- .../Github/index.ts => drivers/github.ts} | 17 +- .../Google/index.ts => drivers/google.ts} | 25 +- .../index.ts => drivers/linked_in.ts} | 17 +- .../Spotify/index.ts => drivers/spotify.ts} | 17 +- .../Twitter/index.ts => drivers/twitter.ts} | 17 +- src/drivers_collection.ts | 68 ++ src/exceptions.ts | 22 + src/redirect_request.ts | 66 ++ src/types.ts | 637 ++++++++++++++++ standalone.ts | 16 - test-helpers/contracts.ts | 32 - test-helpers/index.ts | 41 -- test/ally-manager.spec.ts | 68 -- tsconfig.json | 11 +- 61 files changed, 1541 insertions(+), 2112 deletions(-) delete mode 100644 .adonisrc.json delete mode 100644 .bin/test.js delete mode 100644 .github/COMMIT_CONVENTION.md delete mode 100644 .github/CONTRIBUTING.md delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/labels.json create mode 100644 .github/workflows/checks.yml delete mode 100644 .github/workflows/test.yml delete mode 100644 adonis-typings/ally.ts delete mode 100644 adonis-typings/container.ts delete mode 100644 adonis-typings/context.ts delete mode 100644 adonis-typings/index.ts delete mode 100644 bin/japaTypes.ts delete mode 100644 examples/config/app.ts create mode 100644 index.ts delete mode 100644 instructions.ts delete mode 100644 providers/AllyProvider.ts create mode 100644 providers/ally_provider.ts delete mode 100644 src/Ally/index.ts delete mode 100644 src/AllyManager/index.ts delete mode 100644 src/Exceptions/index.ts delete mode 100644 src/RedirectRequest/index.ts rename src/{AbstractDrivers/Oauth1/index.ts => abstract_drivers/oauth1.ts} (82%) rename src/{AbstractDrivers/Oauth2/index.ts => abstract_drivers/oauth2.ts} (80%) create mode 100644 src/ally_manager.ts create mode 100644 src/bindings/http_context.ts create mode 100644 src/bindings/types.ts rename src/{Config/index.ts => defaults/config.ts} (100%) create mode 100644 src/define_config.ts rename src/{Drivers/Discord/index.ts => drivers/discord.ts} (91%) rename src/{Drivers/Facebook/index.ts => drivers/facebook.ts} (91%) rename src/{Drivers/Github/index.ts => drivers/github.ts} (93%) rename src/{Drivers/Google/index.ts => drivers/google.ts} (88%) rename src/{Drivers/LinkedIn/index.ts => drivers/linked_in.ts} (92%) rename src/{Drivers/Spotify/index.ts => drivers/spotify.ts} (89%) rename src/{Drivers/Twitter/index.ts => drivers/twitter.ts} (90%) create mode 100644 src/drivers_collection.ts create mode 100644 src/exceptions.ts create mode 100644 src/redirect_request.ts create mode 100644 src/types.ts delete mode 100644 standalone.ts delete mode 100644 test-helpers/contracts.ts delete mode 100644 test-helpers/index.ts delete mode 100644 test/ally-manager.spec.ts diff --git a/.adonisrc.json b/.adonisrc.json deleted file mode 100644 index 9836545..0000000 --- a/.adonisrc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "typescript": true, - "providers": ["@adonisjs/core", "./providers/AllyProvider"], - "preloads": [ - "./examples/discord", - "./examples/github", - "./examples/twitter", - "./examples/google", - "./examples/linkedin", - "./examples/facebook", - "./examples/spotify" - ], - "directories": { - "config": "./examples/config" - } -} diff --git a/.bin/test.js b/.bin/test.js deleted file mode 100644 index e20c2dd..0000000 --- a/.bin/test.js +++ /dev/null @@ -1,7 +0,0 @@ -require('@adonisjs/require-ts/build/register') - -const { configure } = require('japa') - -configure({ - files: ['test/**/*.spec.ts'], -}) diff --git a/.github/COMMIT_CONVENTION.md b/.github/COMMIT_CONVENTION.md deleted file mode 100644 index fc852af..0000000 --- a/.github/COMMIT_CONVENTION.md +++ /dev/null @@ -1,70 +0,0 @@ -## Git Commit Message Convention - -> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular). - -Using conventional commit messages, we can automate the process of generating the CHANGELOG file. All commits messages will automatically be validated against the following regex. - -``` js -/^(revert: )?(feat|fix|docs|style|refactor|perf|test|workflow|ci|chore|types|build|improvement)((.+))?: .{1,50}/ -``` - -## Commit Message Format -A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**: - -> The **scope** is optional - -``` -feat(router): add support for prefix - -Prefix makes it easier to append a path to a group of routes -``` - -1. `feat` is type. -2. `router` is scope and is optional -3. `add support for prefix` is the subject -4. The **body** is followed by a blank line. -5. The optional **footer** can be added after the body, followed by a blank line. - -## Types -Only one type can be used at a time and only following types are allowed. - -- feat -- fix -- docs -- style -- refactor -- perf -- test -- workflow -- ci -- chore -- types -- build - -If a type is `feat`, `fix` or `perf`, then the commit will appear in the CHANGELOG.md file. However if there is any BREAKING CHANGE, the commit will always appear in the changelog. - -### Revert -If the commit reverts a previous commit, it should begin with `revert:`, followed by the header of the reverted commit. In the body it should say: `This reverts commit `., where the hash is the SHA of the commit being reverted. - -## Scope -The scope could be anything specifying place of the commit change. For example: `router`, `view`, `querybuilder`, `database`, `model` and so on. - -## Subject -The subject contains succinct description of the change: - -- use the imperative, present tense: "change" not "changed" nor "changes". -- don't capitalize first letter -- no dot (.) at the end - -## Body - -Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes". -The body should include the motivation for the change and contrast this with previous behavior. - -## Footer - -The footer should contain any information about **Breaking Changes** and is also the place to -reference GitHub issues that this commit **Closes**. - -**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this. - diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index f0c5446..0000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributing - -AdonisJS is a community driven project. You are free to contribute in any of the following ways. - -- [Coding style](coding-style) -- [Fix bugs by creating PR's](fix-bugs-by-creating-prs) -- [Share an RFC for new features or big changes](share-an-rfc-for-new-features-or-big-changes) -- [Report security issues](report-security-issues) -- [Be a part of the community](be-a-part-of-community) - -## Coding style - -Majority of AdonisJS core packages are written in Typescript. Having a brief knowledge of Typescript is required to contribute to the core. - -## Fix bugs by creating PR's - -We appreciate every time you report a bug in the framework or related libraries. However, taking time to submit a PR can help us in fixing bugs quickly and ensure a healthy and stable eco-system. - -Go through the following points, before creating a new PR. - -1. Create an issue discussing the bug or short-coming in the framework. -2. Once approved, go ahead and fork the REPO. -3. Make sure to start from the `develop`, since this is the upto date branch. -4. Make sure to keep commits small and relevant. -5. We follow [conventional-commits](https://github.com/conventional-changelog/conventional-changelog) to structure our commit messages. Instead of running `git commit`, you must run `npm commit`, which will show you prompts to create a valid commit message. -6. Once done with all the changes, create a PR against the `develop` branch. - -## Share an RFC for new features or big changes - -Sharing PR's for small changes works great. However, when contributing big features to the framework, it is required to go through the RFC process. - -### What is an RFC? - -RFC stands for **Request for Commits**, a standard process followed by many other frameworks including [Ember](https://github.com/emberjs/rfcs), [yarn](https://github.com/yarnpkg/rfcs) and [rust](https://github.com/rust-lang/rfcs). - -In brief, RFC process allows you to talk about the changes with everyone in the community and get a view of the core team before dedicating your time to work on the feature. - -The RFC proposals are created as Pull Request on [adonisjs/rfcs](https://github.com/adonisjs/rfcs) repo. Make sure to read the README to learn about the process in depth. - -## Report security issues - -All of the security issues, must be reported via [email](mailto:virk@adonisjs.com) and not using any of the public channels. - -## Be a part of community - -We welcome you to participate in [GitHub Discussion](https://github.com/adonisjs/core/discussions) and the AdonisJS [Discord Server](https://discord.gg/vDcEjq6). You are free to ask your questions and share your work or contributions made to AdonisJS eco-system. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index e65000c..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: Bug report -about: Report identified bugs ---- - - - -## Prerequisites - -We do our best to reply to all the issues on time. If you will follow the given guidelines, the turn around time will be faster. - -- Lots of raised issues are directly not bugs but instead are design decisions taken by us. -- Make use of our [GH discussions](https://github.com/adonisjs/core/discussions), or [discord server](https://discord.me/adonisjs), if you are not sure that you are reporting a bug. -- Ensure the issue isn't already reported. -- Ensure you are reporting the bug in the correct repo. - -*Delete the above section and the instructions in the sections below before submitting* - -## Package version - - -## Node.js and npm version - - -## Sample Code (to reproduce the issue) - - -## BONUS (a sample repo to reproduce the issue) - diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index abd44a5..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: Feature request -about: Propose changes for adding a new feature ---- - - - -## Prerequisites - -We do our best to reply to all the issues on time. If you will follow the given guidelines, the turn around time will be faster. - -## Consider an RFC - -Please create an [RFC](https://github.com/adonisjs/rfcs) instead, if - -- Feature introduces a breaking change -- Demands lots of time and changes in the current code base. - -*Delete the above section and the instructions in the sections below before submitting* - -## Why this feature is required (specific use-cases will be appreciated)? - - -## Have you tried any other work arounds? - - -## Are you willing to work on it with little guidance? - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index cceec95..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,28 +0,0 @@ - - -## Proposed changes - -Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. - -## Types of changes - -What types of changes does your code introduce? - -_Put an `x` in the boxes that apply_ - -- [ ] Bugfix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - -## Checklist - -_Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ - -- [ ] I have read the [CONTRIBUTING](https://github.com/adonisjs/ally/blob/master/.github/CONTRIBUTING.md) doc -- [ ] Lint and unit tests pass locally with my changes -- [ ] I have added tests that prove my fix is effective or that my feature works. -- [ ] I have added necessary documentation (if appropriate) - -## Further comments - -If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... diff --git a/.github/labels.json b/.github/labels.json new file mode 100644 index 0000000..ba001c6 --- /dev/null +++ b/.github/labels.json @@ -0,0 +1,170 @@ +[ + { + "name": "Priority: Critical", + "color": "ea0056", + "description": "The issue needs urgent attention", + "aliases": [] + }, + { + "name": "Priority: High", + "color": "5666ed", + "description": "Look into this issue before picking up any new work", + "aliases": [] + }, + { + "name": "Priority: Medium", + "color": "f4ff61", + "description": "Try to fix the issue for the next patch/minor release", + "aliases": [] + }, + { + "name": "Priority: Low", + "color": "87dfd6", + "description": "Something worth considering, but not a top priority for the team", + "aliases": [] + }, + { + "name": "Semver: Alpha", + "color": "008480", + "description": "Will make it's way to the next alpha version of the package", + "aliases": [] + }, + { + "name": "Semver: Major", + "color": "ea0056", + "description": "Has breaking changes", + "aliases": [] + }, + { + "name": "Semver: Minor", + "color": "fbe555", + "description": "Mainly new features and improvements", + "aliases": [] + }, + { + "name": "Semver: Next", + "color": "5666ed", + "description": "Will make it's way to the bleeding edge version of the package", + "aliases": [] + }, + { + "name": "Semver: Patch", + "color": "87dfd6", + "description": "A bug fix", + "aliases": [] + }, + { + "name": "Status: Abandoned", + "color": "ffffff", + "description": "Dropped and not into consideration", + "aliases": ["wontfix"] + }, + { + "name": "Status: Accepted", + "color": "e5fbf2", + "description": "The proposal or the feature has been accepted for the future versions", + "aliases": [] + }, + { + "name": "Status: Blocked", + "color": "ea0056", + "description": "The work on the issue or the PR is blocked. Check comments for reasoning", + "aliases": [] + }, + { + "name": "Status: Completed", + "color": "008672", + "description": "The work has been completed, but not released yet", + "aliases": [] + }, + { + "name": "Status: In Progress", + "color": "73dbc4", + "description": "Still banging the keyboard", + "aliases": ["in progress"] + }, + { + "name": "Status: On Hold", + "color": "f4ff61", + "description": "The work was started earlier, but is on hold now. Check comments for reasoning", + "aliases": ["On Hold"] + }, + { + "name": "Status: Review Needed", + "color": "fbe555", + "description": "Review from the core team is required before moving forward", + "aliases": [] + }, + { + "name": "Status: Awaiting More Information", + "color": "89f8ce", + "description": "Waiting on the issue reporter or PR author to provide more information", + "aliases": [] + }, + { + "name": "Status: Need Contributors", + "color": "7057ff", + "description": "Looking for contributors to help us move forward with this issue or PR", + "aliases": [] + }, + { + "name": "Type: Bug", + "color": "ea0056", + "description": "The issue has indentified a bug", + "aliases": ["bug"] + }, + { + "name": "Type: Security", + "color": "ea0056", + "description": "Spotted security vulnerability and is a top priority for the core team", + "aliases": [] + }, + { + "name": "Type: Duplicate", + "color": "00837e", + "description": "Already answered or fixed previously", + "aliases": ["duplicate"] + }, + { + "name": "Type: Enhancement", + "color": "89f8ce", + "description": "Improving an existing feature", + "aliases": ["enhancement"] + }, + { + "name": "Type: Feature Request", + "color": "483add", + "description": "Request to add a new feature to the package", + "aliases": [] + }, + { + "name": "Type: Invalid", + "color": "dbdbdb", + "description": "Doesn't really belong here. Maybe use discussion threads?", + "aliases": ["invalid"] + }, + { + "name": "Type: Question", + "color": "eceafc", + "description": "Needs clarification", + "aliases": ["help wanted", "question"] + }, + { + "name": "Type: Documentation Change", + "color": "7057ff", + "description": "Documentation needs some improvements", + "aliases": ["documentation"] + }, + { + "name": "Type: Dependencies Update", + "color": "00837e", + "description": "Bump dependencies", + "aliases": ["dependencies"] + }, + { + "name": "Good First Issue", + "color": "008480", + "description": "Want to contribute? Just filter by this label", + "aliases": ["good first issue"] + } +] diff --git a/.github/stale.yml b/.github/stale.yml index 7a6a571..f767674 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -6,10 +6,10 @@ daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - - "Type: Security" + - 'Type: Security' # Label to use when marking an issue as stale -staleLabel: "Status: Abandoned" +staleLabel: 'Status: Abandoned' # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..c27fb04 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,14 @@ +name: checks +on: + - push + - pull_request + +jobs: + test: + uses: adonisjs/.github/.github/workflows/test.yml@main + + lint: + uses: adonisjs/.github/.github/workflows/lint.yml@main + + typecheck: + uses: adonisjs/.github/.github/workflows/typecheck.yml@main diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 8b778ca..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: test -on: - - push - - pull_request -jobs: - linux: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: - - 14.15.4 - - 17.x - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - name: Install - run: npm install - - name: Run tests - run: npm test diff --git a/.husky/commit-msg b/.husky/commit-msg index 4654c12..4002db7 100755 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -1,3 +1,4 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" -HUSKY_GIT_PARAMS=$1 node ./node_modules/@adonisjs/mrm-preset/validate-commit/conventional/validate.js +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npx --no -- commitlint --edit diff --git a/LICENSE.md b/LICENSE.md index 1c19428..381426b 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # The MIT License -Copyright 2022 Harminder Virk, contributors +Copyright (c) 2023 Harminder Virk 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: diff --git a/adonis-typings/ally.ts b/adonis-typings/ally.ts deleted file mode 100644 index 18e95ed..0000000 --- a/adonis-typings/ally.ts +++ /dev/null @@ -1,692 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare module '@ioc:Adonis/Addons/Ally' { - import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' - import { ApplicationContract } from '@ioc:Adonis/Core/Application' - import { - Oauth2AccessToken, - Oauth1RequestToken, - Oauth1AccessToken, - Oauth1ClientConfig, - Oauth2ClientConfig, - ApiRequestContract, - RedirectRequestContract as ClientRequestContract, - } from '@poppinss/oauth-client' - - export { Oauth2AccessToken } - export { Oauth1AccessToken } - export { Oauth1RequestToken } - export { ApiRequestContract } - export { Oauth2ClientConfig as Oauth2DriverConfig } - export { Oauth1ClientConfig as Oauth1DriverConfig } - - /** - * Issue: https://github.com/Microsoft/TypeScript/issues/29729 - * Solution: https://github.com/sindresorhus/type-fest/blob/main/source/literal-union.d.ts - */ - export type LiteralStringUnion = LiteralType | (string & { _?: never }) - - /** - * Extension of oauth-client redirect request with support - * for defining scopes as first class citizen - */ - export interface RedirectRequestContract - extends ClientRequestContract { - /** - * Define a callback to transform scopes before they are defined - * as a param - */ - transformScopes(callback: (scopes: LiteralStringUnion[]) => string[]): this - - /** - * Define the scopes for authorization - */ - scopes(scopes: LiteralStringUnion[]): this - - /** - * Merge to existing pre-defined scopes - */ - mergeScopes(scopes: LiteralStringUnion[]): this - - /** - * Clear existing scopes - */ - clearScopes(): this - } - - /** - * The user fetched from the oauth provider. - */ - export interface AllyUserContract { - id: string - nickName: string - name: string - email: string | null - emailVerificationState: 'verified' | 'unverified' | 'unsupported' - avatarUrl: string | null - token: Token - original: any - } - - /** - * Every driver should implement this contract - */ - export interface AllyDriverContract< - Token extends Oauth2AccessToken | Oauth1AccessToken, - Scopes extends string - > { - version: 'oauth1' | 'oauth2' - - /** - * Perform stateless authentication. Only applicable for Oauth2 clients - */ - stateless(): this - - /** - * Redirect user for authorization - */ - redirect(callback?: (request: RedirectRequestContract) => void): Promise - - /** - * Get redirect url. You must manage the state yourself when redirecting - * manually - */ - redirectUrl(callback?: (request: RedirectRequestContract) => void): Promise - - /** - * Find if the current request has authorization code or oauth token - */ - hasCode(): boolean - - /** - * Get the current request authorization code or oauth token. Returns - * null if there no code - */ - getCode(): string | null - - /** - * Find if the current error code is for access denied - */ - accessDenied(): boolean - - /** - * Find if there is a state mismatch - */ - stateMisMatch(): boolean - - /** - * Find if there is an error post redirect - */ - hasError(): boolean - - /** - * Get the post redirect error - */ - getError(): string | null - - /** - * Get access token - */ - accessToken(callback?: (request: ApiRequestContract) => void): Promise - - /** - * Returns details for the authorized user - */ - user(callback?: (request: ApiRequestContract) => void): Promise> - - /** - * Finds the user by access token. Applicable with "Oauth2" only - */ - userFromToken( - token: string, - callback?: (request: ApiRequestContract) => void - ): Promise> - - /** - * Finds the user by access token. Applicable with "Oauth1" only - */ - userFromTokenAndSecret( - token: string, - secret: string, - callback?: (request: ApiRequestContract) => void - ): Promise> - } - - /** - * ---------------------------------------- - * Discord driver - * ---------------------------------------- - */ - - /** - * Available discord scopes - * https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes - */ - export type DiscordScopes = - | 'activities.read' - | 'activities.write' - | 'applications.builds.read' - | 'applications.builds.upload' - | 'applications.commands' - | 'applications.commands.update' - | 'applications.entitlements' - | 'applications.store.update' - | 'bot' - | 'connections' - | 'email' - | 'gdm.join' - | 'guilds' - | 'guilds.join' - | 'identify' - | 'messages.read' - | 'relationships.read' - | 'rpc' - | 'rpc.activities.write' - | 'rpc.notifications.read' - | 'rpc.voice.read' - | 'rpc.voice.write' - | 'webhook.incoming' - - /** - * Shape of the Discord access token - */ - export type DiscordToken = { - token: string - type: string - scope: string - expiresIn: number - expiresAt: Exclude - refreshToken: string - } - - /** - * Extra options available for Discord - */ - export type DiscordDriverConfig = Oauth2ClientConfig & { - driver: 'discord' - userInfoUrl?: string - scopes?: LiteralStringUnion[] - prompt?: 'consent' | 'none' - guildId?: `${bigint}` // a snowflake - disableGuildSelect?: boolean - permissions?: number - } - - export interface DiscordDriverContract extends AllyDriverContract { - version: 'oauth2' - } - - /** - * ---------------------------------------- - * Github driver - * ---------------------------------------- - */ - - /** - * Available github scopes - * https://docs.github.com/en/free-pro-team@latest/developers/apps/scopes-for-oauth-apps#available-scopes - */ - export type GithubScopes = - | 'repo' - | 'repo:status' - | 'repo_deployment' - | 'public_repo' - | 'repo:invite' - | 'security_events' - | 'admin:repo_hook' - | 'write:repo_hook' - | 'read:repo_hook' - | 'admin:org' - | 'write:org' - | 'read:org' - | 'admin:public_key' - | 'write:public_key' - | 'read:public_key' - | 'admin:org_hook' - | 'gist' - | 'notifications' - | 'user' - | 'read:user' - | 'user:email' - | 'user:follow' - | 'delete_repo' - | 'write:discussion' - | 'read:discussion' - | 'write:packages' - | 'read:packages' - | 'delete:packages' - | 'admin:gpg_key' - | 'write:gpg_key' - | 'read:gpg_key' - | 'workflow' - - /** - * Shape of the Github access token - */ - export type GithubToken = { - token: string - type: string - scope: string - } - - /** - * Extra options available for Github - */ - export type GithubDriverConfig = Oauth2ClientConfig & { - driver: 'github' - login?: string - scopes?: LiteralStringUnion[] - allowSignup?: boolean - userInfoUrl?: string - userEmailUrl?: string - } - - export interface GithubDriverContract extends AllyDriverContract { - version: 'oauth2' - } - - /** - * ---------------------------------------- - * Twitter driver - * ---------------------------------------- - */ - - /** - * Shape of the twitter token - */ - export type TwitterToken = { - token: string - secret: string - userId: string - screenName: string - } - - /** - * Extra options available for twitter - */ - export type TwitterDriverConfig = Oauth1ClientConfig & { - driver: 'twitter' - userInfoUrl?: string - } - - export interface TwitterDriverContract extends AllyDriverContract { - version: 'oauth1' - } - - /** - * ---------------------------------------- - * Google driver - * ---------------------------------------- - */ - - /** - * Most popular google scopes. You can find rest of them on the following link - * https://developers.google.com/identity/protocols/oauth2/scopes - */ - export type GoogleScopes = - | 'userinfo.email' - | 'userinfo.profile' - | 'openid' - | 'contacts' - | 'contacts.other.readonly' - | 'contacts.readonly' - | 'directory.readonly' - | 'user.addresses.read' - | 'user.birthday.read' - | 'user.emails.read' - | 'user.gender.read' - | 'user.organization.read' - | 'user.phonenumbers.read' - | 'analytics' - | 'analytics.readonly' - | 'documents' - | 'documents.readonly' - | 'forms' - | 'forms.currentonly' - | 'groups' - | 'spreadsheets' - | 'calendar' - | 'calendar.events' - | 'calendar.events.readonly' - | 'calendar.readonly' - | 'calendar.settings.readonly' - | 'drive' - | 'drive.appdata' - | 'drive.file' - | 'drive.metadata' - | 'drive.metadata.readonly' - | 'drive.photos.readonly' - | 'drive.readonly' - | 'drive.scripts' - - /** - * Shape of the Google access token - */ - export type GoogleToken = Oauth2AccessToken & { - /** - * @deprecated - * Use `idToken` instead - */ - id_token: string - idToken: string - grantedScopes: string[] - } - - /** - * Config accepted by the google driver. Most of the options can be - * overwritten at runtime - * https://developers.google.com/identity/protocols/oauth2/openid-connect#re-consent - */ - export type GoogleDriverConfig = Oauth2ClientConfig & { - driver: 'google' - userInfoUrl?: string - - /** - * Can be configured at runtime - */ - scopes?: LiteralStringUnion[] - prompt?: 'none' | 'consent' | 'select_account' - accessType?: 'online' | 'offline' - hostedDomain?: string - display?: 'page' | 'popup' | 'touch' | 'wrap' - } - - export interface GoogleDriverContract extends AllyDriverContract { - version: 'oauth2' - } - - /** - * ---------------------------------------- - * LinkedIn driver - * ---------------------------------------- - */ - - /** - * A list of LinkedIn scopes. You can find the scopes available - * to your app from the LinkedIn dasbhoard. - * https://www.linkedin.com/developers/apps//auth - */ - export type LinkedInScopes = - | 'r_emailaddress' - | 'r_liteprofile' - | 'w_member_social' - | 'r_fullprofile' - | 'r_basicprofile_app' - | 'r_primarycontact' - | 'rw_organization_admin' - - /** - * Shape of the Linked access token - */ - export type LinkedInToken = { - token: string - type: string - expiresIn: number - expiresAt: Exclude - } - - /** - * Config accepted by the linkedin driver. Most of the options can be - * overwritten at runtime - * https://docs.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow?context=linkedin%2Fcontext&tabs=HTTPS#step-2-request-an-authorization-code - */ - export type LinkedInDriverConfig = Oauth2ClientConfig & { - driver: 'linkedin' - userInfoUrl?: string - userEmailUrl?: string - - /** - * Can be configured at runtime - */ - scopes?: LiteralStringUnion[] - } - - export interface LinkedInDriverContract - extends AllyDriverContract { - version: 'oauth2' - } - - /** - * ---------------------------------------- - * Facebook driver - * ---------------------------------------- - */ - - /** - * Most popular facebook scopes. You can find rest of them on the following link - * https://developers.facebook.com/docs/permissions/reference/ - */ - export type FacebookScopes = - | 'ads_management' - | 'ads_read' - | 'attribution_read' - | 'business_management' - | 'catalog_management' - | 'email' - | 'groups_access_member_info' - | 'leads_retrieval' - | 'pages_events' - | 'pages_manage_ads' - | 'pages_manage_cta' - | 'pages_manage_instant_articles' - | 'pages_manage_engagement' - | 'pages_manage_metadata' - | 'pages_manage_posts' - | 'pages_messaging' - | 'pages_read_engagement' - | 'pages_read_user_content' - | 'pages_show_list' - | 'pages_user_gender' - | 'pages_user_locale' - | 'pages_user_timezone' - | 'public_profile' - | 'publish_to_groups' - | 'publish_video' - | 'read_insights' - | 'user_age_range' - | 'user_birthday' - | 'user_friends' - | 'user_gender' - | 'user_hometown' - | 'user_likes' - | 'user_link' - | 'user_location' - | 'user_photos' - | 'user_posts' - | 'user_videos' - - /** - * Most used user profile fields. - * For more visit https://developers.facebook.com/docs/graph-api/reference/user - */ - export type FacebookProfileFields = - | 'id' - | 'first_name' - | 'last_name' - | 'middle_name' - | 'name' - | 'name_format' - | 'picture' - | 'short_name' - | 'verified' - | 'birthday' - | 'email' - | 'gender' - | 'link' - - /** - * Shape of the Facebook access token - */ - export type FacebookToken = { - token: string - type: string - expiresIn: number - expiresAt: Exclude - } - - /** - * Config accepted by the facebook driver. Most of the options can be - * overwritten at runtime - * https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow - */ - export type FacebookDriverConfig = Oauth2ClientConfig & { - driver: 'facebook' - userInfoUrl?: string - - /** - * Can be configured at runtime - */ - scopes?: LiteralStringUnion[] - userFields?: LiteralStringUnion[] - display?: string - authType?: string - } - - export interface FacebookDriverContract - extends AllyDriverContract { - version: 'oauth2' - } - - /** - * ---------------------------------------- - * Spotify driver - * ---------------------------------------- - */ - - /** - * Available spotify scopes - * https://developer.spotify.com/documentation/general/guides/scopes/ - */ - export type SpotifyScopes = - | 'ugc-image-upload' - | 'user-read-recently-played' - | 'user-top-read' - | 'user-read-playback-position' - | 'user-read-playback-state' - | 'user-modify-playback-state' - | 'user-read-currently-playing' - | 'app-remote-control' - | 'streaming' - | 'playlist-modify-public' - | 'playlist-modify-private' - | 'playlist-read-private' - | 'playlist-read-collaborative' - | 'user-follow-modify' - | 'user-follow-read' - | 'user-library-modify' - | 'user-library-read' - | 'user-read-email' - | 'user-read-private' - - /** - * Shape of the Spotify access token - */ - export type SpotifyToken = { - token: string - type: string - refreshToken: string - expiresIn: number - expiresAt: Exclude - } - - /** - * Extra options available for Spotify - */ - export type SpotifyDriverConfig = Oauth2ClientConfig & { - driver: 'spotify' - scopes?: LiteralStringUnion[] - showDialog?: boolean - } - - export interface SpotifyDriverContract extends AllyDriverContract { - version: 'oauth2' - } - - /** - * END OF DRIVERS - */ - - /** - * Must be defined in user land - */ - export interface SocialProviders {} - - /** - * Ally config - */ - export type AllyConfig = { - [Provider in keyof SocialProviders]: SocialProviders[Provider]['config'] - } - - /** - * Shape of the callback to extend the ally drivers - */ - export type ExtendDriverCallback = ( - ally: AllyManagerContract, - mapping: string, - config: any, - ctx: HttpContextContract - ) => AllyDriverContract - - /** - * Ally instance is passed to every HTTP request and has access to the - * current request - */ - export interface AllyContract { - /** - * Get driver instance of a named mapping - */ - use( - provider: Provider - ): SocialProviders[Provider]['implementation'] - - /** - * Get driver instance of an unnamed mapping - */ - use(provider: string): AllyDriverContract - } - - /** - * Ally Manager manages the lifecycle of Ally drivers - */ - export interface AllyManagerContract { - application: ApplicationContract - - /** - * Make instance of a named mapping - */ - makeMapping( - ctx: HttpContextContract, - mapping: keyof SocialProviders - ): SocialProviders[Provider]['implementation'] - - /** - * Make instance of an unnamed mapping - */ - makeMapping( - ctx: HttpContextContract, - mapping: string - ): AllyDriverContract - - /** - * Returns an instance of ally, which can be later used to - * get instances of social providers for a given request - */ - getAllyForRequest(ctx: HttpContextContract): AllyContract - - /** - * Extend ally by adding new drivers - */ - extend(driverName: string, callback: ExtendDriverCallback): void - } - - const Ally: AllyManagerContract - export default Ally -} diff --git a/adonis-typings/container.ts b/adonis-typings/container.ts deleted file mode 100644 index f6c069d..0000000 --- a/adonis-typings/container.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare module '@ioc:Adonis/Core/Application' { - import { AllyManagerContract } from '@ioc:Adonis/Addons/Ally' - export interface ContainerBindings { - 'Adonis/Addons/Ally': AllyManagerContract - } -} diff --git a/adonis-typings/context.ts b/adonis-typings/context.ts deleted file mode 100644 index 6eb542d..0000000 --- a/adonis-typings/context.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare module '@ioc:Adonis/Core/HttpContext' { - import { AllyContract } from '@ioc:Adonis/Addons/Ally' - export interface HttpContextContract { - ally: AllyContract - } -} diff --git a/adonis-typings/index.ts b/adonis-typings/index.ts deleted file mode 100644 index ff3289f..0000000 --- a/adonis-typings/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -/// -/// -/// diff --git a/bin/japaTypes.ts b/bin/japaTypes.ts deleted file mode 100644 index d42cac6..0000000 --- a/bin/japaTypes.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Assert } from '@japa/assert' - -declare module '@japa/runner' { - interface TestContext { - assert: Assert - } -} diff --git a/bin/test.ts b/bin/test.ts index 5aba7ce..1b83905 100644 --- a/bin/test.ts +++ b/bin/test.ts @@ -1,7 +1,5 @@ import { assert } from '@japa/assert' -import { specReporter } from '@japa/spec-reporter' -import { runFailedTests } from '@japa/run-failed-tests' -import { processCliArgs, configure, run } from '@japa/runner' +import { processCLIArgs, configure, run } from '@japa/runner' /* |-------------------------------------------------------------------------- @@ -16,14 +14,10 @@ import { processCliArgs, configure, run } from '@japa/runner' | | Please consult japa.dev/runner-config for the config docs. */ +processCLIArgs(process.argv.slice(2)) configure({ - ...processCliArgs(process.argv.slice(2)), - ...{ - files: ['test/**/*.spec.ts'], - plugins: [assert(), runFailedTests()], - reporters: [specReporter()], - importer: (filePath: string) => import(filePath), - }, + files: ['test/**/*.spec.ts'], + plugins: [assert()], }) /* diff --git a/examples/app.ts b/examples/app.ts index 0a2f450..e1a05a2 100644 --- a/examples/app.ts +++ b/examples/app.ts @@ -1,18 +1,49 @@ -import { join } from 'path' -import { createServer } from 'http' -import { Application } from '@adonisjs/core/build/standalone' +import { Env } from '@adonisjs/core/env' +import { IgnitorFactory } from '@adonisjs/core/factories' + +const APP_ROOT = new URL('./', import.meta.url) +const IMPORTER = (filePath: string) => { + if (filePath.startsWith('./') || filePath.startsWith('../')) { + return import(new URL(filePath, APP_ROOT).href) + } + return import(filePath) +} + +await Env.create(new URL('../', APP_ROOT), {}) +const allyConfig = await import('./config/ally.js') async function run() { - const app = new Application(join(__dirname, '../'), 'web') - await app.setup() - await app.registerProviders() - await app.bootProviders() - await app.requirePreloads() + const ignitor = new IgnitorFactory() + .withCoreConfig() + .withCoreProviders() + .merge({ + config: { + ally: allyConfig.default, + }, + }) + .merge({ + rcFileContents: { + providers: ['../providers/ally_provider.js'], + preloads: [ + './discord.js', + './github.js', + './twitter.js', + './google.js', + './linkedin.js', + './facebook.js', + './spotify.js', + ], + }, + }) + .create(APP_ROOT, { + importer: IMPORTER, + }) - const server = app.container.use('Adonis/Core/Server') - server.optimize() - const port = process.env.PORT ? parseInt(process.env.PORT) : 3000 - createServer(server.handle.bind(server)).listen(port) + await ignitor.httpServer().start() } run() + .catch(console.error) + .then(() => { + console.log('ready') + }) diff --git a/examples/config/ally.ts b/examples/config/ally.ts index 79893fa..c4d1ff9 100644 --- a/examples/config/ally.ts +++ b/examples/config/ally.ts @@ -1,49 +1,52 @@ -import Env from '@ioc:Adonis/Core/Env' -import { AllyConfig } from '@ioc:Adonis/Addons/Ally' +import { defineConfig } from '../../src/define_config.js' -const allyConfig: AllyConfig = { +const allyConfig = defineConfig({ discord: { driver: 'discord', - clientId: Env.get('DISCORD_CLIENT_ID'), - clientSecret: Env.get('DISCORD_CLIENT_SECRET'), - callbackUrl: `http://localhost:${Env.get('PORT')}/discord/callback`, + clientId: process.env.DISCORD_CLIENT_ID!, + clientSecret: process.env.DISCORD_CLIENT_SECRET!, + callbackUrl: `http://localhost:${process.env.PORT}/discord/callback`, }, google: { driver: 'google', - clientId: Env.get('GOOGLE_CLIENT_ID'), - clientSecret: Env.get('GOOGLE_CLIENT_SECRET'), - callbackUrl: `http://localhost:${Env.get('PORT')}/google/callback`, + clientId: process.env.GOOGLE_CLIENT_ID!, + clientSecret: process.env.GOOGLE_CLIENT_SECRET!, + callbackUrl: `http://localhost:${process.env.PORT}/google/callback`, }, github: { driver: 'github', - clientId: Env.get('GITHUB_CLIENT_ID'), - clientSecret: Env.get('GITHUB_CLIENT_SECRET'), - callbackUrl: `http://localhost:${Env.get('PORT')}/github/callback`, + clientId: process.env.GITHUB_CLIENT_ID!, + clientSecret: process.env.GITHUB_CLIENT_SECRET!, + callbackUrl: `http://localhost:${process.env.PORT}/github/callback`, }, linkedin: { driver: 'linkedin', - clientId: Env.get('LINKEDIN_CLIENT_ID'), - clientSecret: Env.get('LINKEDIN_CLIENT_SECRET'), - callbackUrl: `http://localhost:${Env.get('PORT')}/linkedin/callback`, + clientId: process.env.LINKEDIN_CLIENT_ID!, + clientSecret: process.env.LINKEDIN_CLIENT_SECRET!, + callbackUrl: `http://localhost:${process.env.PORT}/linkedin/callback`, }, twitter: { driver: 'twitter', - clientId: Env.get('TWITTER_API_KEY'), - clientSecret: Env.get('TWITTER_APP_SECRET'), - callbackUrl: `http://localhost:${Env.get('PORT')}/twitter/callback`, + clientId: process.env.TWITTER_API_KEY!, + clientSecret: process.env.TWITTER_APP_SECRET!, + callbackUrl: `http://localhost:${process.env.PORT}/twitter/callback`, }, facebook: { driver: 'facebook', - clientId: Env.get('FACEBOOK_CLIENT_ID'), - clientSecret: Env.get('FACEBOOK_CLIENT_SECRET'), - callbackUrl: `http://localhost:${Env.get('PORT')}/facebook/callback`, + clientId: process.env.FACEBOOK_CLIENT_ID!, + clientSecret: process.env.FACEBOOK_CLIENT_SECRET!, + callbackUrl: `http://localhost:${process.env.PORT}/facebook/callback`, }, spotify: { driver: 'spotify', - clientId: Env.get('SPOTIFY_CLIENT_ID'), - clientSecret: Env.get('SPOTIFY_CLIENT_SECRET'), - callbackUrl: `http://localhost:${Env.get('PORT')}/spotify/callback`, + clientId: process.env.SPOTIFY_CLIENT_ID!, + clientSecret: process.env.SPOTIFY_CLIENT_SECRET!, + callbackUrl: `http://localhost:${process.env.PORT}/spotify/callback`, }, +}) + +declare module '@adonisjs/ally/types' { + interface SocialProviders extends InferSocialProviders {} } export default allyConfig diff --git a/examples/config/app.ts b/examples/config/app.ts deleted file mode 100644 index 6d1bb98..0000000 --- a/examples/config/app.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const appKey = 'averylongrandomsecretkey' -export const http = { - trustProxy: () => true, - cookie: {}, -} - -export const logger = { - enabled: true, - level: 'trace', -} diff --git a/examples/discord.ts b/examples/discord.ts index 2beac37..b95ac78 100644 --- a/examples/discord.ts +++ b/examples/discord.ts @@ -7,19 +7,19 @@ * file that was distributed with this source code. */ -import Route from '@ioc:Adonis/Core/Route' +import router from '@adonisjs/core/services/router' -Route.get('discord', async ({ response }) => { +router.get('discord', async ({ response }) => { return response.send(' Login with Discord') }) -Route.get('/discord/redirect', async ({ ally }) => { +router.get('/discord/redirect', async ({ ally }) => { return ally.use('discord').redirect((request) => { request.scopes(['identify', 'guilds']) }) }) -Route.get('/discord/callback', async ({ ally }) => { +router.get('/discord/callback', async ({ ally }) => { try { const discord = ally.use('discord') if (discord.accessDenied()) { diff --git a/examples/facebook.ts b/examples/facebook.ts index 97182c6..31bd5a4 100644 --- a/examples/facebook.ts +++ b/examples/facebook.ts @@ -7,19 +7,19 @@ * file that was distributed with this source code. */ -import Route from '@ioc:Adonis/Core/Route' +import router from '@adonisjs/core/services/router' -Route.get('facebook', async ({ response }) => { +router.get('facebook', async ({ response }) => { return response.send(' Login with facebook ') }) -Route.get('/facebook/redirect', async ({ ally }) => { +router.get('/facebook/redirect', async ({ ally }) => { return ally.use('facebook').redirect((request) => { request.scopes(['email']) }) }) -Route.get('/facebook/callback', async ({ ally }) => { +router.get('/facebook/callback', async ({ ally }) => { try { const facebook = ally.use('facebook') if (facebook.accessDenied()) { diff --git a/examples/github.ts b/examples/github.ts index 7c48d94..4d41282 100644 --- a/examples/github.ts +++ b/examples/github.ts @@ -7,19 +7,19 @@ * file that was distributed with this source code. */ -import Route from '@ioc:Adonis/Core/Route' +import router from '@adonisjs/core/services/router' -Route.get('github', async ({ response }) => { +router.get('github', async ({ response }) => { return response.send(' Login with Github ') }) -Route.get('/github/redirect', async ({ ally }) => { +router.get('/github/redirect', async ({ ally }) => { return ally.use('github').redirect((request) => { request.scopes(['read:user']) }) }) -Route.get('/github/callback', async ({ ally }) => { +router.get('/github/callback', async ({ ally }) => { try { const gh = ally.use('github') if (gh.accessDenied()) { @@ -37,7 +37,7 @@ Route.get('/github/callback', async ({ ally }) => { const user = await gh.user() return user } catch (error) { - console.log({ error: error.response }) + console.log({ error: error.cause }) throw error } }) diff --git a/examples/google.ts b/examples/google.ts index ba7aac0..4825a24 100644 --- a/examples/google.ts +++ b/examples/google.ts @@ -7,17 +7,17 @@ * file that was distributed with this source code. */ -import Route from '@ioc:Adonis/Core/Route' +import router from '@adonisjs/core/services/router' -Route.get('google', async ({ response }) => { +router.get('google', async ({ response }) => { return response.send(' Login with Google ') }) -Route.get('/google/redirect', async ({ ally }) => { +router.get('/google/redirect', async ({ ally }) => { return ally.use('google').redirect() }) -Route.get('/google/callback', async ({ ally }) => { +router.get('/google/callback', async ({ ally }) => { try { const google = ally.use('google') if (google.accessDenied()) { diff --git a/examples/linkedin.ts b/examples/linkedin.ts index afd5a65..d519887 100644 --- a/examples/linkedin.ts +++ b/examples/linkedin.ts @@ -7,17 +7,17 @@ * file that was distributed with this source code. */ -import Route from '@ioc:Adonis/Core/Route' +import router from '@adonisjs/core/services/router' -Route.get('linkedin', async ({ response }) => { +router.get('linkedin', async ({ response }) => { return response.send(' Login with linkedin ') }) -Route.get('/linkedin/redirect', async ({ ally }) => { +router.get('/linkedin/redirect', async ({ ally }) => { return ally.use('linkedin').redirect() }) -Route.get('/linkedin/callback', async ({ ally }) => { +router.get('/linkedin/callback', async ({ ally }) => { try { const linkedin = ally.use('linkedin') if (linkedin.accessDenied()) { diff --git a/examples/spotify.ts b/examples/spotify.ts index b76749b..ce6692a 100644 --- a/examples/spotify.ts +++ b/examples/spotify.ts @@ -1,16 +1,25 @@ -import Route from '@ioc:Adonis/Core/Route' +/* + * @adonisjs/ally + * + * (c) Harminder Virk + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ -Route.get('spotify', async ({ response }) => { +import router from '@adonisjs/core/services/router' + +router.get('spotify', async ({ response }) => { return response.send(' Login with spotify ') }) -Route.get('/spotify/redirect', async ({ ally }) => { +router.get('/spotify/redirect', async ({ ally }) => { return ally.use('spotify').redirect((request) => { request.scopes(['user-read-email']) }) }) -Route.get('/spotify/callback', async ({ ally }) => { +router.get('/spotify/callback', async ({ ally }) => { try { const spotify = ally.use('spotify') if (spotify.accessDenied()) { diff --git a/examples/twitter.ts b/examples/twitter.ts index 8ed76c1..7efa5c8 100644 --- a/examples/twitter.ts +++ b/examples/twitter.ts @@ -7,17 +7,17 @@ * file that was distributed with this source code. */ -import Route from '@ioc:Adonis/Core/Route' +import router from '@adonisjs/core/services/router' -Route.get('twitter', async ({ response }) => { +router.get('twitter', async ({ response }) => { return response.send(' Login with twitter ') }) -Route.get('/twitter/redirect', async ({ ally }) => { +router.get('/twitter/redirect', async ({ ally }) => { return ally.use('twitter').redirect() }) -Route.get('/twitter/callback', async ({ ally, request }) => { +router.get('/twitter/callback', async ({ ally, request }) => { console.log(request.cookiesList()) try { diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..9f1a573 --- /dev/null +++ b/index.ts @@ -0,0 +1,19 @@ +/* + * @adonisjs/ally + * + * (c) Harminder Virk + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import './src/bindings/types.js' + +export { HttpClient as ApiRequest } from '@poppinss/oauth-client' + +export { Oauth2Driver } from './src/abstract_drivers/oauth2.js' +export { Oauth1Driver } from './src/abstract_drivers/oauth1.js' +export { AllyManager } from './src/ally_manager.js' +export { default as driversList } from './src/drivers_collection.js' +export * as errors from './src/exceptions.js' +export { RedirectRequest } from './src/redirect_request.js' diff --git a/instructions.ts b/instructions.ts deleted file mode 100644 index be9d066..0000000 --- a/instructions.ts +++ /dev/null @@ -1,237 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import * as sinkStatic from '@adonisjs/sink' -import { ApplicationContract } from '@ioc:Adonis/Core/Application' - -type InstructionsState = { - providers: { - github: boolean - google: boolean - twitter: boolean - discord: boolean - linkedin: boolean - facebook: boolean - spotify: boolean - } - envVars: typeof ENV_VARS -} - -/** - * Base path to config stub partials - */ -const CONFIG_PARTIALS_BASE = './config/partials' - -/** - * Prompt choices for the provider selection - */ -const PROVIDER_PROMPT_CHOICES = [ - { - name: 'github' as const, - message: 'Github', - }, - { - name: 'google' as const, - message: 'Google', - }, - { - name: 'twitter' as const, - message: 'Twitter', - }, - { - name: 'discord' as const, - message: 'Discord', - }, - { - name: 'linkedin' as const, - message: 'LinkedIn', - }, - { - name: 'facebook' as const, - message: 'Facebook', - }, - { - name: 'spotify' as const, - message: 'Spotify', - }, -] - -/** - * Environment variables for available providers - */ -const ENV_VARS = { - github: { - clientId: 'GITHUB_CLIENT_ID', - clientSecret: 'GITHUB_CLIENT_SECRET', - }, - google: { - clientId: 'GOOGLE_CLIENT_ID', - clientSecret: 'GOOGLE_CLIENT_SECRET', - }, - twitter: { - clientId: 'TWITTER_CLIENT_ID', - clientSecret: 'TWITTER_CLIENT_SECRET', - }, - discord: { - clientId: 'DISCORD_CLIENT_ID', - clientSecret: 'DISCORD_CLIENT_SECRET', - }, - linkedin: { - clientId: 'LINKEDIN_CLIENT_ID', - clientSecret: 'LINKEDIN_CLIENT_SECRET', - }, - facebook: { - clientId: 'FACEBOOK_CLIENT_ID', - clientSecret: 'FACEBOOK_CLIENT_SECRET', - }, - spotify: { - clientId: 'SPOTIFY_CLIENT_ID', - clientSecret: 'SPOTIFY_CLIENT_SECRET', - }, -} - -/** - * Returns absolute path to the stub relative from the templates - * directory - */ -function getStub(...relativePaths: string[]) { - return join(__dirname, 'templates', ...relativePaths) -} - -/** - * Creates the contract file - */ -function makeContract( - projectRoot: string, - app: ApplicationContract, - sink: typeof sinkStatic, - state: InstructionsState -) { - const contractsDirectory = app.directoriesMap.get('contracts') || 'contracts' - const contractPath = join(contractsDirectory, 'ally.ts') - - const template = new sink.files.MustacheFile( - projectRoot, - contractPath, - getStub('contracts/ally.txt') - ) - template.overwrite = true - - template.apply(state).commit() - sink.logger.action('create').succeeded(contractPath) -} - -/** - * Makes the auth config file - */ -function makeConfig( - projectRoot: string, - app: ApplicationContract, - sink: typeof sinkStatic, - state: InstructionsState -) { - const configDirectory = app.directoriesMap.get('config') || 'config' - const configPath = join(configDirectory, 'ally.ts') - - const template = new sink.files.MustacheFile(projectRoot, configPath, getStub('config/ally.txt')) - template.overwrite = true - - /** - * Compute partials from selected providers - */ - const partials: any = {} - Object.keys(state.providers).forEach((provider) => { - if (state.providers[provider] === true) { - partials[`${provider}_provider`] = getStub(CONFIG_PARTIALS_BASE, `${provider}.txt`) - } - }) - - template.apply(state).partials(partials).commit() - sink.logger.action('create').succeeded(configPath) -} - -/** - * Define environment variables based upon user selection - */ -function defineEnvVars(projectRoot: string, sink: typeof sinkStatic, state: InstructionsState) { - const env = new sink.files.EnvFile(projectRoot) - - Object.keys(state.providers).forEach((provider) => { - if (state.providers[provider] === true) { - env.set(state.envVars[provider].clientId, 'clientId') - env.set(state.envVars[provider].clientSecret, 'clientSecret') - } else { - env.unset(state.envVars[provider].clientId) - env.unset(state.envVars[provider].clientSecret) - } - }) - - env.commit() - sink.logger.action('update').succeeded('.env,.env.example') -} - -/** - * Prompts user to select the provider - */ -async function getProvider(sink: typeof sinkStatic) { - return sink - .getPrompt() - .multiple('Select social providers you are planning to use', PROVIDER_PROMPT_CHOICES, { - validate(choice) { - return choice && choice.length ? true : 'Select at least one provider' - }, - }) -} - -/** - * Instructions to be executed when setting up the package. - */ -export default async function instructions( - projectRoot: string, - app: ApplicationContract, - sink: typeof sinkStatic -) { - const state: InstructionsState = { - providers: { - github: false, - google: false, - twitter: false, - discord: false, - linkedin: false, - facebook: false, - spotify: false, - }, - envVars: ENV_VARS, - } - - const selectedProviders = await getProvider(sink) - state.providers.discord = selectedProviders.includes('discord') - state.providers.github = selectedProviders.includes('github') - state.providers.google = selectedProviders.includes('google') - state.providers.twitter = selectedProviders.includes('twitter') - state.providers.linkedin = selectedProviders.includes('linkedin') - state.providers.facebook = selectedProviders.includes('facebook') - state.providers.spotify = selectedProviders.includes('spotify') - - /** - * Make contract file - */ - makeContract(projectRoot, app, sink, state) - - /** - * Make config file - */ - makeConfig(projectRoot, app, sink, state) - - /** - * Define env vars - */ - defineEnvVars(projectRoot, sink, state) -} diff --git a/package.json b/package.json index 4785087..7afae93 100644 --- a/package.json +++ b/package.json @@ -1,154 +1,108 @@ { - "name": "@adonisjs/ally", - "version": "4.1.5", - "description": "Social authentication provider for AdonisJS", - "types": "./build/adonis-typings/index.d.ts", - "main": "build/providers/AllyProvider.js", - "files": [ - "build/adonis-typings", - "build/providers", - "build/templates", - "build/src", - "build/instructions.js", - "build/instructions.md", - "build/standalone.d.ts", - "build/standalone.js" - ], - "scripts": { - "mrm": "mrm --preset=@adonisjs/mrm-preset", - "pretest": "npm run lint", - "test": "node -r @adonisjs/require-ts/build/register bin/test.ts", - "clean": "del-cli build", - "start": "node -r @adonisjs/assembler/build/register examples/app.ts", - "copyfiles": "copyfiles \"templates/**/*.txt\" \"instructions.md\" build", - "compile": "npm run lint && npm run clean && tsc", - "build": "npm run compile && npm run copyfiles", - "prepublishOnly": "npm run build", - "lint": "eslint . --ext=.ts", - "format": "prettier --write .", - "commit": "git-cz", - "release": "np --message=\"chore(release): %s\"", - "version": "npm run build", - "sync-labels": "github-label-sync --labels ./node_modules/@adonisjs/mrm-preset/gh-labels.json adonisjs/ally" - }, - "keywords": [ - "adonis", - "adonisjs", - "social-auth", - "oauth" - ], - "author": "adonisjs,virk", - "license": "MIT", - "devDependencies": { - "@adonisjs/assembler": "^5.9.3", - "@adonisjs/core": "^5.8.7", - "@adonisjs/mrm-preset": "^5.0.3", - "@adonisjs/require-ts": "^2.0.13", - "@japa/assert": "^1.3.6", - "@japa/run-failed-tests": "^1.1.0", - "@japa/runner": "^2.2.1", - "@japa/spec-reporter": "^1.3.1", - "@poppinss/dev-utils": "^2.0.3", - "@types/node": "^18.8.4", - "commitizen": "^4.2.5", - "copyfiles": "^2.4.1", - "cz-conventional-changelog": "^3.3.0", - "del-cli": "^5.0.0", - "dotenv": "^16.0.3", - "eslint": "^8.25.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-adonis": "^2.1.1", - "eslint-plugin-prettier": "^4.2.1", - "github-label-sync": "^2.2.0", - "husky": "^8.0.1", - "mrm": "^4.1.6", - "nock": "^13.2.9", - "np": "^7.6.2", - "prettier": "^2.7.1", - "typescript": "^4.8.4" - }, - "husky": { - "hooks": { - "commit-msg": "node ./node_modules/@adonisjs/mrm-preset/validateCommit/conventional/validate.js" - } - }, - "config": { - "commitizen": { - "path": "cz-conventional-changelog" - } - }, - "np": { - "contents": ".", - "anyBranch": false - }, - "dependencies": { - "@poppinss/oauth-client": "^4.0.2", - "@poppinss/utils": "^5.0.0" - }, - "peerDependencies": { - "@adonisjs/core": "^5.8.0" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/adonisjs/adonis-ally.git" - }, - "bugs": { - "url": "https://github.com/adonisjs/adonis-ally/issues" - }, - "homepage": "https://github.com/adonisjs/adonis-ally#readme", - "publishConfig": { - "tag": "latest", - "access": "public" - }, - "adonisjs": { - "instructions": "./build/instructions.js", - "instructionsMd": "./build/instructions.md", - "types": "@adonisjs/ally", - "providers": [ - "@adonisjs/ally" - ] - }, - "mrmConfig": { - "core": true, - "license": "MIT", - "services": [ - "github-actions" - ], - "minNodeVersion": "14.15.4", - "probotApps": [ - "stale", - "lock" - ], - "runGhActionsOnWindows": false - }, - "eslintConfig": { - "extends": [ - "plugin:adonis/typescriptPackage", - "prettier" - ], - "plugins": [ - "prettier" - ], - "rules": { - "prettier/prettier": [ - "error", - { - "endOfLine": "auto" - } - ] - } - }, - "eslintIgnore": [ - "build" - ], - "prettier": { - "trailingComma": "es5", - "semi": false, - "singleQuote": true, - "useTabs": false, - "quoteProps": "consistent", - "bracketSpacing": true, - "arrowParens": "always", - "printWidth": 100 - } + "name": "@adonisjs/ally", + "version": "4.1.5", + "description": "Social authentication provider for AdonisJS", + "type": "module", + "main": "build/index.js", + "files": [ + "build/src", + "build/index.d.ts", + "build/index.js" + ], + "exports": { + ".": "./build/index.js", + "./types": "./build/src/types.js" + }, + "engines": { + "node": ">=18.16.0" + }, + "scripts": { + "pretest": "npm run lint", + "test": "c8 npm run quick:test", + "clean": "del-cli build", + "typecheck": "tsc --noEmit", + "start": "node -r @adonisjs/assembler/build/register examples/app.ts", + "copyfiles": "copyfiles \"templates/**/*.txt\" \"instructions.md\" build", + "compile": "npm run lint && npm run clean && tsc", + "build": "npm run compile && npm run copyfiles", + "prepublishOnly": "npm run build", + "lint": "eslint . --ext=.ts", + "format": "prettier --write .", + "release": "np", + "version": "npm run build", + "sync-labels": "github-label-sync --labels .github/labels.json adonisjs/ally", + "quick:test": "node --loader=ts-node/esm bin/test.ts" + }, + "keywords": [ + "adonis", + "adonisjs", + "social-auth", + "oauth" + ], + "author": "adonisjs,virk", + "license": "MIT", + "devDependencies": { + "@adonisjs/core": "^6.1.5-8", + "@adonisjs/eslint-config": "^1.1.8", + "@adonisjs/prettier-config": "^1.1.8", + "@adonisjs/tsconfig": "^1.1.8", + "@commitlint/cli": "^17.6.6", + "@commitlint/config-conventional": "^17.6.6", + "@japa/assert": "^2.0.0-1", + "@japa/runner": "^3.0.0-6", + "@swc/core": "^1.3.69", + "@types/node": "^20.4.2", + "c8": "^8.0.0", + "copyfiles": "^2.4.1", + "del-cli": "^5.0.0", + "dotenv": "^16.3.1", + "eslint": "^8.45.0", + "github-label-sync": "^2.3.1", + "husky": "^8.0.3", + "nock": "^13.3.2", + "np": "^8.0.4", + "prettier": "^3.0.0", + "ts-node": "^10.9.1", + "typescript": "^5.1.6" + }, + "dependencies": { + "@poppinss/oauth-client": "^5.1.0-3", + "@poppinss/utils": "^6.5.0-3" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/adonisjs/adonis-ally.git" + }, + "bugs": { + "url": "https://github.com/adonisjs/adonis-ally/issues" + }, + "homepage": "https://github.com/adonisjs/adonis-ally#readme", + "publishConfig": { + "access": "public", + "tag": "next" + }, + "np": { + "message": "chore(release): %s", + "tag": "next", + "branch": "main", + "anyBranch": false + }, + "prettier": "@adonisjs/prettier-config", + "eslintConfig": { + "extends": "@adonisjs/eslint-config/package" + }, + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ] + }, + "c8": { + "reporter": [ + "text", + "html" + ], + "exclude": [ + "tests/**" + ] + } } diff --git a/providers/AllyProvider.ts b/providers/AllyProvider.ts deleted file mode 100644 index 98eea92..0000000 --- a/providers/AllyProvider.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -/// - -import { ApplicationContract } from '@ioc:Adonis/Core/Application' -import { AllyManager } from '../src/AllyManager' - -/** - * Ally provider to hook into an AdonisJS application - */ -export default class AllyProvider { - protected static needsApplication = true - constructor(private application: ApplicationContract) {} - - /** - * Register the binding - */ - public register() { - this.application.container.singleton('Adonis/Addons/Ally', (container) => { - const config = container.resolveBinding('Adonis/Core/Config').get('ally', {}) - return new AllyManager(this.application, config) - }) - } - - /** - * Stick an instance to the current HTTP request - */ - public boot() { - this.application.container.withBindings( - ['Adonis/Core/HttpContext', 'Adonis/Addons/Ally'], - (HttpContext, Ally) => { - HttpContext.getter( - 'ally', - function ally() { - return Ally.getAllyForRequest(this) - }, - true - ) - } - ) - } -} diff --git a/providers/ally_provider.ts b/providers/ally_provider.ts new file mode 100644 index 0000000..e4b6599 --- /dev/null +++ b/providers/ally_provider.ts @@ -0,0 +1,22 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { ApplicationService } from '@adonisjs/core/types' +import { extendHttpContext } from '../src/bindings/http_context.js' + +/** + * AllyProvider extends the HTTP context with the "ally" property + */ +export default class AllyProvider { + constructor(protected app: ApplicationService) {} + + async boot() { + extendHttpContext(this.app.config.get('ally')) + } +} diff --git a/src/Ally/index.ts b/src/Ally/index.ts deleted file mode 100644 index c67a2fb..0000000 --- a/src/Ally/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -/// - -import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' -import { - AllyContract, - Oauth1AccessToken, - Oauth2AccessToken, - AllyDriverContract, - AllyManagerContract, -} from '@ioc:Adonis/Addons/Ally' - -/** - * Ally allows constructing drivers for a given HTTP request. "use" is the only - * method we need. - */ -export class Ally implements AllyContract { - /** - * All drivers are cached during a given HTTP request - */ - private mappingsCache: Map< - string, - AllyDriverContract - > = new Map() - - constructor(private manager: AllyManagerContract, private ctx: HttpContextContract) {} - - /** - * Returns an instance of an ally driver. Driver instances are singleton during - * a given HTTP request - */ - public use(provider: string) { - if (!this.mappingsCache.has(provider)) { - this.mappingsCache.set(provider, this.manager.makeMapping(this.ctx, provider)) - } - - return this.mappingsCache.get(provider)! - } -} diff --git a/src/AllyManager/index.ts b/src/AllyManager/index.ts deleted file mode 100644 index 941ae80..0000000 --- a/src/AllyManager/index.ts +++ /dev/null @@ -1,179 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { Exception } from '@poppinss/utils' -import { ApplicationContract } from '@ioc:Adonis/Core/Application' -import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' -import { - AllyContract, - SocialProviders, - GithubDriverConfig, - AllyManagerContract, - ExtendDriverCallback, - DiscordDriverConfig, - TwitterDriverConfig, - GoogleDriverConfig, - LinkedInDriverConfig, - FacebookDriverConfig, - SpotifyDriverConfig, -} from '@ioc:Adonis/Addons/Ally' - -import { Ally } from '../Ally' - -/** - * Manages the lifecycle of ally drivers and instantiates new instances - * for a given HTTP request - */ -export class AllyManager implements AllyManagerContract { - /** - * Extended set of ally drivers - */ - private extendedDrivers: Map = new Map() - - constructor(public application: ApplicationContract, private config: any) {} - - /** - * Returns the config for a given mapping from the config file - */ - protected getMappingConfig(name: string) { - const config = this.config[name] - - if (!config) { - throw new Exception( - `Missing config for social provider "${name}". Make sure it is defined inside the "config/ally" file` - ) - } - - if (!config.driver) { - throw new Exception(`Missing driver property on "${name}" provider config`) - } - - return config - } - - /** - * Make the discord driver - */ - protected makeDiscord(config: DiscordDriverConfig, ctx: HttpContextContract) { - const { DiscordDriver } = require('../Drivers/Discord') - return new DiscordDriver(ctx, config) - } - - /** - * Make the github driver - */ - protected makeGithub(config: GithubDriverConfig, ctx: HttpContextContract) { - const { GithubDriver } = require('../Drivers/Github') - return new GithubDriver(ctx, config) - } - - /** - * Make the twitter driver - */ - protected makeTwitter(config: TwitterDriverConfig, ctx: HttpContextContract) { - const { TwitterDriver } = require('../Drivers/Twitter') - return new TwitterDriver(ctx, config) - } - - /** - * Make the google driver - */ - protected makeGoogle(config: GoogleDriverConfig, ctx: HttpContextContract) { - const { GoogleDriver } = require('../Drivers/Google') - return new GoogleDriver(ctx, config) - } - - /** - * Make the linkedin driver - */ - protected makeLinkedIn(config: LinkedInDriverConfig, ctx: HttpContextContract) { - const { LinkedInDriver } = require('../Drivers/LinkedIn') - return new LinkedInDriver(ctx, config) - } - - /** - * Make the facebook driver - */ - protected makeFacebook(config: FacebookDriverConfig, ctx: HttpContextContract) { - const { FacebookDriver } = require('../Drivers/Facebook') - return new FacebookDriver(ctx, config) - } - - /** - * Make the spotify driver - */ - - protected makeSpotify(config: SpotifyDriverConfig, ctx: HttpContextContract) { - const { SpotifyDriver } = require('../Drivers/Spotify') - return new SpotifyDriver(ctx, config) - } - - /** - * Makes an instance of the extended driver - */ - protected makeExtendedDriver(mapping: string, config: any, ctx: HttpContextContract) { - const extendedCallback = this.extendedDrivers.get(config.driver) - if (typeof extendedCallback === 'function') { - return extendedCallback(this, mapping, config, ctx) - } - - throw new Exception(`Unknown ally driver "${config.driver}"`) - } - - /** - * Returns an instance of a mapping - */ - protected makeMappingInstance(mapping: string, ctx: HttpContextContract) { - const config = this.getMappingConfig(mapping) - switch (config.driver) { - case 'discord': - return this.makeDiscord(config, ctx) - case 'github': - return this.makeGithub(config, ctx) - case 'twitter': - return this.makeTwitter(config, ctx) - case 'google': - return this.makeGoogle(config, ctx) - case 'linkedin': - return this.makeLinkedIn(config, ctx) - case 'facebook': - return this.makeFacebook(config, ctx) - case 'spotify': - return this.makeSpotify(config, ctx) - default: - return this.makeExtendedDriver(mapping, config, ctx) - } - } - - /** - * Makes an instance of a given mapping - */ - public makeMapping(ctx: HttpContextContract, mapping: keyof SocialProviders) { - return this.makeMappingInstance(mapping, ctx) - } - - /** - * Returns an instance of ally, which can be later used to - * get instances of social providers for a given request - */ - public getAllyForRequest(ctx: HttpContextContract): AllyContract { - return new Ally(this, ctx) - } - - /** - * Add a new custom ally driver - */ - public extend(driverName: string, callback: ExtendDriverCallback): void { - if (typeof callback !== 'function') { - throw new Exception('"Ally.extend" expects callback to be a function') - } - - this.extendedDrivers.set(driverName, callback) - } -} diff --git a/src/Exceptions/index.ts b/src/Exceptions/index.ts deleted file mode 100644 index 2d75ecb..0000000 --- a/src/Exceptions/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { Exception } from '@poppinss/utils' - -export class OauthException extends Exception { - public static missingAuthorizationCode(paramName: string) { - return new this( - `Cannot request access token. Redirect request is missing the "${paramName}" param`, - 500, - 'E_OAUTH_MISSING_CODE' - ) - } - - /** - * Unable to verify state after redirect - */ - public static stateMisMatch() { - return new this('Unable to verify re-redirect state', 400, 'E_OAUTH_STATE_MISMATCH') - } -} diff --git a/src/RedirectRequest/index.ts b/src/RedirectRequest/index.ts deleted file mode 100644 index f416f00..0000000 --- a/src/RedirectRequest/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { UrlBuilder } from '@poppinss/oauth-client' -import { RedirectRequestContract, LiteralStringUnion } from '@ioc:Adonis/Addons/Ally' - -/** - * Redirect request with first class support for defining scopes. - */ -export class RedirectRequest - extends UrlBuilder - implements RedirectRequestContract -{ - private scopesTransformer: undefined | ((scopes: LiteralStringUnion[]) => string[]) - - constructor(baseUrl: string, private scopeParamName: string, private scopeSeparator: string) { - super(baseUrl) - } - - public transformScopes(callback: (scopes: LiteralStringUnion[]) => string[]): this { - this.scopesTransformer = callback - return this - } - - /** - * Define an array of scopes. - */ - public scopes(scopes: LiteralStringUnion[]): this { - if (typeof this.scopesTransformer === 'function') { - scopes = this.scopesTransformer(scopes) - } - - this.param(this.scopeParamName, scopes.join(this.scopeSeparator)) - return this - } - - /** - * Clear existing scopes - */ - public clearScopes(): this { - this.clearParam(this.scopeParamName) - return this - } - - /** - * Merge to existing scopes - */ - public mergeScopes(scopes: LiteralStringUnion[]): this { - if (typeof this.scopesTransformer === 'function') { - scopes = this.scopesTransformer(scopes) - } - - const mergedScopes = (this.params[this.scopeParamName] || []).concat(scopes) - this.scopes(mergedScopes) - - return this - } -} diff --git a/src/AbstractDrivers/Oauth1/index.ts b/src/abstract_drivers/oauth1.ts similarity index 82% rename from src/AbstractDrivers/Oauth1/index.ts rename to src/abstract_drivers/oauth1.ts index ea40f8e..87d03ae 100644 --- a/src/AbstractDrivers/Oauth1/index.ts +++ b/src/abstract_drivers/oauth1.ts @@ -7,11 +7,9 @@ * file that was distributed with this source code. */ -/// - import { Exception } from '@poppinss/utils' import { Oauth1Client } from '@poppinss/oauth-client' -import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import type { HttpContext } from '@adonisjs/core/http' import { AllyUserContract, @@ -20,10 +18,10 @@ import { ApiRequestContract, AllyDriverContract, RedirectRequestContract, -} from '@ioc:Adonis/Addons/Ally' +} from '../types.js' -import { OauthException } from '../../Exceptions' -import { RedirectRequest } from '../../RedirectRequest' +import * as errors from '../exceptions.js' +import { RedirectRequest } from '../redirect_request.js' /** * Abstract implementation for an Oauth1 driver @@ -91,14 +89,12 @@ export abstract class Oauth1Driver void - ): Promise> + abstract user(callback?: (request: ApiRequestContract) => void): Promise> /** * Finds the user by access token */ - public abstract userFromTokenAndSecret( + abstract userFromTokenAndSecret( token: string, secret: string, callback?: (request: ApiRequestContract) => void @@ -107,12 +103,12 @@ export abstract class Oauth1Driver) => void ): Promise { - return this.getRedirectUrl(callback) + return this.getRedirectUrl(callback as any) } /** * Redirect user for authorization. */ - public async redirect( - callback?: (request: RedirectRequestContract) => void - ): Promise { + async redirect(callback?: (request: RedirectRequestContract) => void): Promise { const { token, secret } = await this.getRequestToken() /** * Storing token and secret inside cookies. We need them * later */ - this.persistToken(token) - this.persistSecret(secret) + this.#persistToken(token) + this.#persistSecret(secret) const url = await this.redirectUrl((request) => { request.param(this.oauthTokenParamName, token) @@ -229,21 +226,21 @@ export abstract class Oauth1Driver void): Promise { + async accessToken(callback?: (request: ApiRequestContract) => void): Promise { /** * We expect the user to handle errors before calling this method */ if (this.hasError()) { - throw OauthException.missingAuthorizationCode(this.oauthTokenVerifierName) + throw new errors.E_OAUTH_MISSING_CODE([this.oauthTokenVerifierName]) } /** @@ -286,7 +283,7 @@ export abstract class Oauth1Driver { + async userFromToken(): Promise { throw new Exception( '"userFromToken" is not available with Oauth1. Use "userFromTokenAndSecret" instead' ) diff --git a/src/AbstractDrivers/Oauth2/index.ts b/src/abstract_drivers/oauth2.ts similarity index 80% rename from src/AbstractDrivers/Oauth2/index.ts rename to src/abstract_drivers/oauth2.ts index ea22c64..15f0a39 100644 --- a/src/AbstractDrivers/Oauth2/index.ts +++ b/src/abstract_drivers/oauth2.ts @@ -1,17 +1,15 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -/// - import { Exception } from '@poppinss/utils' import { Oauth2Client } from '@poppinss/oauth-client' -import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import type { HttpContext } from '@adonisjs/core/http' import { AllyUserContract, @@ -20,10 +18,10 @@ import { ApiRequestContract, AllyDriverContract, RedirectRequestContract, -} from '@ioc:Adonis/Addons/Ally' +} from '../types.js' -import { OauthException } from '../../Exceptions' -import { RedirectRequest } from '../../RedirectRequest' +import * as errors from '../exceptions.js' +import { RedirectRequest } from '../redirect_request.js' /** * Abstract implementation for an Oauth2 driver @@ -93,14 +91,12 @@ export abstract class Oauth2Driver void - ): Promise> + abstract user(callback?: (request: ApiRequestContract) => void): Promise> /** * Finds the user by access token */ - public abstract userFromToken( + abstract userFromToken( token: string, callback?: (request: ApiRequestContract) => void ): Promise> @@ -108,19 +104,22 @@ export abstract class Oauth2Driver) => void ): Promise { - const url = this.getRedirectUrl(callback) + const url = this.getRedirectUrl(callback as any) return url } /** * Redirect user for authorization. */ - public async redirect( - callback?: (request: RedirectRequestContract) => void - ): Promise { + async redirect(callback?: (request: RedirectRequestContract) => void): Promise { const url = await this.redirectUrl((request) => { - const state = this.persistState() + const state = this.#persistState() state && request.param(this.stateParamName, state) if (typeof callback === 'function') { @@ -208,7 +205,7 @@ export abstract class Oauth2Driver void): Promise { + async accessToken(callback?: (request: ApiRequestContract) => void): Promise { /** * We expect the user to handle errors before calling this method */ if (this.hasError()) { - throw OauthException.missingAuthorizationCode(this.codeParamName) + throw new errors.E_OAUTH_MISSING_CODE([this.codeParamName]) } /** @@ -269,7 +266,7 @@ export abstract class Oauth2Driver { + async userFromTokenAndSecret(): Promise { throw new Exception( '"userFromTokenAndSecret" is not applicable with Oauth2. Use "userFromToken" instead' ) diff --git a/src/ally_manager.ts b/src/ally_manager.ts new file mode 100644 index 0000000..fa8ae49 --- /dev/null +++ b/src/ally_manager.ts @@ -0,0 +1,52 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { RuntimeException } from '@poppinss/utils' +import type { HttpContext } from '@adonisjs/core/http' +import type { AllyDriverContract, AllyManagerDriverFactory } from './types.js' + +/** + * AllyManager is used to create instances of a social drivers during an + * HTTP request. The drivers are cached during the lifecycle of a request. + */ +export class AllyManager> { + /** + * Config with the list of social providers + */ + #config: KnownSocialProviders + #ctx: HttpContext + #driversCache: Map> = new Map() + + constructor(config: KnownSocialProviders, ctx: HttpContext) { + this.#ctx = ctx + this.#config = config + } + + /** + * Returns the driver instance of a social provider + */ + use( + provider: SocialProvider + ): ReturnType { + if (this.#driversCache.has(provider)) { + return this.#driversCache.get(provider) as ReturnType + } + + const driver = this.#config[provider] + if (!driver) { + throw new RuntimeException( + `Unknown ally provider "${String( + provider + )}". Make sure it is registered inside the config/ally.ts file` + ) + } + + return driver(this.#ctx) as ReturnType + } +} diff --git a/src/bindings/http_context.ts b/src/bindings/http_context.ts new file mode 100644 index 0000000..67db21e --- /dev/null +++ b/src/bindings/http_context.ts @@ -0,0 +1,29 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { HttpContext } from '@adonisjs/core/http' + +import './types.js' +import { AllyManager } from '../ally_manager.js' +import { AllyManagerDriverFactory, SocialProviders } from '../types.js' + +/** + * Extends HttpContext class with the ally getter + */ +export function extendHttpContext( + config: SocialProviders extends Record ? SocialProviders : never +) { + HttpContext.getter( + 'ally', + function (this: HttpContext) { + return new AllyManager(config, this) + }, + true + ) +} diff --git a/src/bindings/types.ts b/src/bindings/types.ts new file mode 100644 index 0000000..23bfa85 --- /dev/null +++ b/src/bindings/types.ts @@ -0,0 +1,25 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import type { AllyManager } from '../ally_manager.js' +import type { AllyManagerDriverFactory, SocialProviders } from '../types.js' + +/** + * In order for types to get picked up, this module must get + * imported by TypeScript. Therefore, we export this module + * from the package entrypoint + */ + +declare module '@adonisjs/core/http' { + interface HttpContext { + ally: AllyManager< + SocialProviders extends Record ? SocialProviders : never + > + } +} diff --git a/src/Config/index.ts b/src/defaults/config.ts similarity index 100% rename from src/Config/index.ts rename to src/defaults/config.ts diff --git a/src/define_config.ts b/src/define_config.ts new file mode 100644 index 0000000..dbe4712 --- /dev/null +++ b/src/define_config.ts @@ -0,0 +1,56 @@ +/* + * @adonisjs/ally + * + * (c) Ally + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import type { HttpContext } from '@adonisjs/core/http' + +import type { AllyDriversList } from './types.js' +import allyDriversCollection from './drivers_collection.js' + +/** + * Define config for the ally + */ +export function defineConfig< + KnownSocialProviders extends Record< + string, + { + [K in keyof AllyDriversList]: { driver: K } & Parameters[0] + }[keyof AllyDriversList] + >, +>( + config: KnownSocialProviders +): { + [K in keyof KnownSocialProviders]: ( + ctx: HttpContext + ) => ReturnType +} { + /** + * Converting user defined config to an object of providers + * that can be injected into the AllyManager class + */ + const managerHashers = Object.keys(config).reduce( + (result, provider: keyof KnownSocialProviders) => { + const providerConfig = config[provider] + result[provider] = (ctx: HttpContext) => { + return allyDriversCollection.create( + providerConfig.driver, + providerConfig, + ctx + ) + } + return result + }, + {} as { + [K in keyof KnownSocialProviders]: ( + ctx: HttpContext + ) => ReturnType + } + ) + + return managerHashers +} diff --git a/src/Drivers/Discord/index.ts b/src/drivers/discord.ts similarity index 91% rename from src/Drivers/Discord/index.ts rename to src/drivers/discord.ts index 1fca1ee..b2bc95a 100644 --- a/src/Drivers/Discord/index.ts +++ b/src/drivers/discord.ts @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import type { HttpContext } from '@adonisjs/core/http' import { DiscordScopes, DiscordToken, @@ -15,8 +15,8 @@ import { DiscordDriverConfig, DiscordDriverContract, RedirectRequestContract, -} from '@ioc:Adonis/Addons/Ally' -import { Oauth2Driver } from '../../AbstractDrivers/Oauth2' +} from '../types.js' +import { Oauth2Driver } from '../abstract_drivers/oauth2.js' /** * Discord driver to login user via Discord @@ -60,7 +60,10 @@ export class DiscordDriver */ protected scopesSeparator = ' ' - constructor(ctx: HttpContextContract, public config: DiscordDriverConfig) { + constructor( + ctx: HttpContext, + public config: DiscordDriverConfig + ) { super(ctx, config) /** @@ -156,7 +159,7 @@ export class DiscordDriver /** * Find if the current error code is for access denied */ - public accessDenied(): boolean { + accessDenied(): boolean { const error = this.getError() if (!error) { return false @@ -168,7 +171,7 @@ export class DiscordDriver /** * Returns details for the authorized user */ - public async user(callback?: (request: ApiRequestContract) => void) { + async user(callback?: (request: ApiRequestContract) => void) { const token = await this.accessToken(callback) const user = await this.getUserInfo(token.token, callback) @@ -181,7 +184,7 @@ export class DiscordDriver /** * Finds the user by the access token */ - public async userFromToken(token: string, callback?: (request: ApiRequestContract) => void) { + async userFromToken(token: string, callback?: (request: ApiRequestContract) => void) { const user = await this.getUserInfo(token, callback) return { diff --git a/src/Drivers/Facebook/index.ts b/src/drivers/facebook.ts similarity index 91% rename from src/Drivers/Facebook/index.ts rename to src/drivers/facebook.ts index b02123f..1534384 100644 --- a/src/Drivers/Facebook/index.ts +++ b/src/drivers/facebook.ts @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import type { HttpContext } from '@adonisjs/core/http' import { FacebookToken, FacebookScopes, @@ -17,8 +17,8 @@ import { FacebookProfileFields, FacebookDriverContract, RedirectRequestContract, -} from '@ioc:Adonis/Addons/Ally' -import { Oauth2Driver } from '../../AbstractDrivers/Oauth2' +} from '../types.js' +import { Oauth2Driver } from '../abstract_drivers/oauth2.js' /** * Facebook driver to login user via Facebook @@ -75,7 +75,10 @@ export class FacebookDriver */ protected scopesSeparator = ' ' - constructor(ctx: HttpContextContract, public config: FacebookDriverConfig) { + constructor( + ctx: HttpContext, + public config: FacebookDriverConfig + ) { super(ctx, config) /** @@ -157,7 +160,7 @@ export class FacebookDriver /** * Find if the current error code is for access denied */ - public accessDenied(): boolean { + accessDenied(): boolean { const error = this.getError() if (!error) { return false @@ -169,7 +172,7 @@ export class FacebookDriver /** * Returns details for the authorized user */ - public async user(callback?: (request: ApiRequestContract) => void) { + async user(callback?: (request: ApiRequestContract) => void) { const token = await this.accessToken(callback) const user = await this.getUserInfo(token.token, callback) @@ -182,7 +185,7 @@ export class FacebookDriver /** * Finds the user by the access token */ - public async userFromToken(token: string, callback?: (request: ApiRequestContract) => void) { + async userFromToken(token: string, callback?: (request: ApiRequestContract) => void) { const user = await this.getUserInfo(token, callback) return { diff --git a/src/Drivers/Github/index.ts b/src/drivers/github.ts similarity index 93% rename from src/Drivers/Github/index.ts rename to src/drivers/github.ts index 57aa6e3..5d4c809 100644 --- a/src/Drivers/Github/index.ts +++ b/src/drivers/github.ts @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import type { HttpContext } from '@adonisjs/core/http' import { GithubToken, GithubScopes, @@ -16,8 +16,8 @@ import { ApiRequestContract, GithubDriverContract, RedirectRequestContract, -} from '@ioc:Adonis/Addons/Ally' -import { Oauth2Driver } from '../../AbstractDrivers/Oauth2' +} from '../types.js' +import { Oauth2Driver } from '../abstract_drivers/oauth2.js' /** * Github driver to login user via Github @@ -62,7 +62,10 @@ export class GithubDriver */ protected scopesSeparator = ' ' - constructor(ctx: HttpContextContract, public config: GithubDriverConfig) { + constructor( + ctx: HttpContext, + public config: GithubDriverConfig + ) { super(ctx, config) /** * Extremely important to call the following method to clear the @@ -194,7 +197,7 @@ export class GithubDriver /** * Find if the current error code is for access denied */ - public accessDenied(): boolean { + accessDenied(): boolean { const error = this.getError() if (!error) { return false @@ -206,7 +209,7 @@ export class GithubDriver /** * Returns details for the authorized user */ - public async user(callback?: (request: ApiRequestContract) => void) { + async user(callback?: (request: ApiRequestContract) => void) { const token = await this.accessToken(callback) const user = await this.getUserInfo(token.token, callback) @@ -234,7 +237,7 @@ export class GithubDriver /** * Finds the user by the access token */ - public async userFromToken(token: string, callback?: (request: ApiRequestContract) => void) { + async userFromToken(token: string, callback?: (request: ApiRequestContract) => void) { const user = await this.getUserInfo(token, callback) /** diff --git a/src/Drivers/Google/index.ts b/src/drivers/google.ts similarity index 88% rename from src/Drivers/Google/index.ts rename to src/drivers/google.ts index df51926..93b1091 100644 --- a/src/Drivers/Google/index.ts +++ b/src/drivers/google.ts @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import type { HttpContext } from '@adonisjs/core/http' import { GoogleToken, GoogleScopes, @@ -15,8 +15,8 @@ import { ApiRequestContract, GoogleDriverContract, RedirectRequestContract, -} from '@ioc:Adonis/Addons/Ally' -import { Oauth2Driver } from '../../AbstractDrivers/Oauth2' +} from '../types.js' +import { Oauth2Driver } from '../abstract_drivers/oauth2.js' const SCOPE_PREFIXES = { 'https://www.googleapis.com/auth': [ @@ -98,7 +98,10 @@ export class GoogleDriver */ protected scopesSeparator = ' ' - constructor(ctx: HttpContextContract, public config: GoogleDriverConfig) { + constructor( + ctx: HttpContext, + public config: GoogleDriverConfig + ) { super(ctx, config) /** * Extremely important to call the following method to clear the @@ -176,7 +179,7 @@ export class GoogleDriver /** * Find if the current error code is for access denied */ - public accessDenied(): boolean { + accessDenied(): boolean { const error = this.getError() if (!error) { return false @@ -188,7 +191,7 @@ export class GoogleDriver /** * Get access token */ - public async accessToken(callback?: (request: ApiRequestContract) => void): Promise { + async accessToken(callback?: (request: ApiRequestContract) => void): Promise { const token = await super.accessToken(callback) return { @@ -200,7 +203,7 @@ export class GoogleDriver /** * Returns details for the authorized user */ - public async user(callback?: (request: ApiRequestContract) => void) { + async user(callback?: (request: ApiRequestContract) => void) { const token = await this.accessToken(callback) const user = await this.getUserInfo(token.token, callback) @@ -213,7 +216,7 @@ export class GoogleDriver /** * Finds the user by the access token */ - public async userFromToken(token: string, callback?: (request: ApiRequestContract) => void) { + async userFromToken(token: string, callback?: (request: ApiRequestContract) => void) { const user = await this.getUserInfo(token, callback) return { @@ -225,9 +228,11 @@ export class GoogleDriver /** * Prefixes google scopes with the url */ - public buildScopes(scopes: string[]) { + buildScopes(scopes: string[]) { return scopes.map((name) => { - const prefix = Object.keys(SCOPE_PREFIXES).find((one) => SCOPE_PREFIXES[one].includes(name)) + const prefix = Object.keys(SCOPE_PREFIXES).find((one) => + SCOPE_PREFIXES[one as keyof typeof SCOPE_PREFIXES].includes(name) + ) return prefix ? `${prefix}/${name}` : name }) } diff --git a/src/Drivers/LinkedIn/index.ts b/src/drivers/linked_in.ts similarity index 92% rename from src/Drivers/LinkedIn/index.ts rename to src/drivers/linked_in.ts index 33a4317..bd41079 100644 --- a/src/Drivers/LinkedIn/index.ts +++ b/src/drivers/linked_in.ts @@ -8,7 +8,7 @@ */ import { Exception } from '@poppinss/utils' -import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import type { HttpContext } from '@adonisjs/core/http' import { LinkedInToken, LinkedInScopes, @@ -16,8 +16,8 @@ import { LinkedInDriverConfig, LinkedInDriverContract, RedirectRequestContract, -} from '@ioc:Adonis/Addons/Ally' -import { Oauth2Driver } from '../../AbstractDrivers/Oauth2' +} from '../types.js' +import { Oauth2Driver } from '../abstract_drivers/oauth2.js' /** * LinkedIn driver to login user via LinkedIn @@ -62,7 +62,10 @@ export class LinkedInDriver */ protected scopesSeparator = ' ' - constructor(ctx: HttpContextContract, public config: LinkedInDriverConfig) { + constructor( + ctx: HttpContext, + public config: LinkedInDriverConfig + ) { super(ctx, config) /** * Extremely important to call the following method to clear the @@ -167,7 +170,7 @@ export class LinkedInDriver /** * Find if the current error code is for access denied */ - public accessDenied(): boolean { + accessDenied(): boolean { const error = this.getError() if (!error) { return false @@ -179,7 +182,7 @@ export class LinkedInDriver /** * Returns details for the authorized user */ - public async user(callback?: (request: ApiRequestContract) => void) { + async user(callback?: (request: ApiRequestContract) => void) { const token = await this.accessToken(callback) const user = await this.getUserInfo(token.token, callback) const email = await this.getUserEmail(token.token, callback) @@ -195,7 +198,7 @@ export class LinkedInDriver /** * Finds the user by the access token */ - public async userFromToken(token: string, callback?: (request: ApiRequestContract) => void) { + async userFromToken(token: string, callback?: (request: ApiRequestContract) => void) { const user = await this.getUserInfo(token, callback) const email = await this.getUserEmail(token, callback) diff --git a/src/Drivers/Spotify/index.ts b/src/drivers/spotify.ts similarity index 89% rename from src/Drivers/Spotify/index.ts rename to src/drivers/spotify.ts index 9081651..daa3865 100644 --- a/src/Drivers/Spotify/index.ts +++ b/src/drivers/spotify.ts @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import type { HttpContext } from '@adonisjs/core/http' import { SpotifyScopes, SpotifyToken, @@ -15,8 +15,8 @@ import { SpotifyDriverConfig, SpotifyDriverContract, RedirectRequestContract, -} from '@ioc:Adonis/Addons/Ally' -import { Oauth2Driver } from '../../AbstractDrivers/Oauth2' +} from '../types.js' +import { Oauth2Driver } from '../abstract_drivers/oauth2.js' /** * Spotify driver to login user via Spotify @@ -60,7 +60,10 @@ export class SpotifyDriver */ protected scopesSeparator = ' ' - constructor(ctx: HttpContextContract, public config: SpotifyDriverConfig) { + constructor( + ctx: HttpContext, + public config: SpotifyDriverConfig + ) { super(ctx, config) /** @@ -127,7 +130,7 @@ export class SpotifyDriver /** * Find if the current error code is for access denied */ - public accessDenied(): boolean { + accessDenied(): boolean { const error = this.getError() if (!error) { return false @@ -139,7 +142,7 @@ export class SpotifyDriver /** * Returns details for the authorized user */ - public async user(callback?: (request: ApiRequestContract) => void) { + async user(callback?: (request: ApiRequestContract) => void) { const token = await this.accessToken(callback) const user = await this.getUserInfo(token.token, callback) @@ -152,7 +155,7 @@ export class SpotifyDriver /** * Finds the user by the access token */ - public async userFromToken(token: string, callback?: (request: ApiRequestContract) => void) { + async userFromToken(token: string, callback?: (request: ApiRequestContract) => void) { const user = await this.getUserInfo(token, callback) return { diff --git a/src/Drivers/Twitter/index.ts b/src/drivers/twitter.ts similarity index 90% rename from src/Drivers/Twitter/index.ts rename to src/drivers/twitter.ts index d27dd73..ce1e5d8 100644 --- a/src/Drivers/Twitter/index.ts +++ b/src/drivers/twitter.ts @@ -7,15 +7,15 @@ * file that was distributed with this source code. */ -import { Oauth1Driver } from '../../AbstractDrivers/Oauth1' +import type { HttpContext } from '@adonisjs/core/http' import { TwitterToken, AllyUserContract, ApiRequestContract, TwitterDriverConfig, TwitterDriverContract, -} from '@ioc:Adonis/Addons/Ally' -import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +} from '../types.js' +import { Oauth1Driver } from '../abstract_drivers/oauth1.js' /** * Twitter driver to login user via twitter @@ -61,7 +61,10 @@ export class TwitterDriver protected scopeParamName = '' protected scopesSeparator = ' ' - constructor(protected ctx: HttpContextContract, public config: TwitterDriverConfig) { + constructor( + protected ctx: HttpContext, + public config: TwitterDriverConfig + ) { super(ctx, config) /** @@ -115,7 +118,7 @@ export class TwitterDriver /** * Returns details for the authorized user */ - public async user(callback?: (request: ApiRequestContract) => void) { + async user(callback?: (request: ApiRequestContract) => void) { const token = await this.accessToken() const userInfo = await this.getUserInfo(token.token, token.secret, callback) @@ -129,7 +132,7 @@ export class TwitterDriver * Finds the user info from the "oauth_token" and "oauth_token_secret" * access from the access token. */ - public async userFromTokenAndSecret( + async userFromTokenAndSecret( token: string, secret: string, callback?: (request: ApiRequestContract) => void @@ -145,7 +148,7 @@ export class TwitterDriver /** * Find if the current error code is for access denied */ - public accessDenied(): boolean { + accessDenied(): boolean { return this.ctx.request.input('denied') } } diff --git a/src/drivers_collection.ts b/src/drivers_collection.ts new file mode 100644 index 0000000..c69cf54 --- /dev/null +++ b/src/drivers_collection.ts @@ -0,0 +1,68 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { RuntimeException } from '@poppinss/utils' +import type { HttpContext } from '@adonisjs/core/http' + +import type { AllyDriversList } from './types.js' +import { GithubDriver } from './drivers/github.js' +import { GoogleDriver } from './drivers/google.js' +import { SpotifyDriver } from './drivers/spotify.js' +import { TwitterDriver } from './drivers/twitter.js' +import { DiscordDriver } from './drivers/discord.js' +import { FacebookDriver } from './drivers/facebook.js' +import { LinkedInDriver } from './drivers/linked_in.js' + +class AllyDriversCollection { + /** + * List of registered drivers + */ + list: Partial = { + discord: (config, ctx) => new DiscordDriver(ctx, config), + facebook: (config, ctx) => new FacebookDriver(ctx, config), + github: (config, ctx) => new GithubDriver(ctx, config), + google: (config, ctx) => new GoogleDriver(ctx, config), + linkedin: (config, ctx) => new LinkedInDriver(ctx, config), + spotify: (config, ctx) => new SpotifyDriver(ctx, config), + twitter: (config, ctx) => new TwitterDriver(ctx, config), + } + + /** + * Extend drivers collection and add a custom + * driver to it. + */ + extend( + driverName: Name, + factoryCallback: AllyDriversList[Name] + ): this { + this.list[driverName] = factoryCallback + return this + } + + /** + * Creates the driver instance with config + */ + create( + name: Name, + config: Parameters[0], + ctx: HttpContext + ): ReturnType { + const driverFactory = this.list[name] + if (!driverFactory) { + throw new RuntimeException( + `Unknown ally driver "${String(name)}". Make sure the driver is registered` + ) + } + + return driverFactory(config as any, ctx) as ReturnType + } +} + +const allyDriversCollection = new AllyDriversCollection() +export default allyDriversCollection diff --git a/src/exceptions.ts b/src/exceptions.ts new file mode 100644 index 0000000..7564a97 --- /dev/null +++ b/src/exceptions.ts @@ -0,0 +1,22 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { createError } from '@poppinss/utils' + +export const E_OAUTH_MISSING_CODE = createError<[string]>( + 'Cannot request access token. Redirect request is missing the "%s" param', + 'E_OAUTH_MISSING_CODE', + 500 +) + +export const E_OAUTH_STATE_MISMATCH = createError( + 'Unable to verify re-redirect state', + 'E_OAUTH_STATE_MISMATCH', + 400 +) diff --git a/src/redirect_request.ts b/src/redirect_request.ts new file mode 100644 index 0000000..78ee669 --- /dev/null +++ b/src/redirect_request.ts @@ -0,0 +1,66 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { UrlBuilder } from '@poppinss/oauth-client' +import { LiteralStringUnion } from './types.js' + +/** + * Redirect request with first class support for defining scopes. + */ +export class RedirectRequest extends UrlBuilder { + #scopesTransformer: undefined | ((scopes: LiteralStringUnion[]) => string[]) + #scopeParamName: string + #scopeSeparator: string + + constructor(baseUrl: string, scopeParamName: string, scopeSeparator: string) { + super(baseUrl) + this.#scopeParamName = scopeParamName + this.#scopeSeparator = scopeSeparator + } + + transformScopes(callback: (scopes: LiteralStringUnion[]) => string[]): this { + this.#scopesTransformer = callback + return this + } + + /** + * Define an array of scopes. + */ + scopes(scopes: LiteralStringUnion[]): this { + if (typeof this.#scopesTransformer === 'function') { + scopes = this.#scopesTransformer(scopes) + } + + this.param(this.#scopeParamName, scopes.join(this.#scopeSeparator)) + return this + } + + /** + * Clear existing scopes + */ + clearScopes(): this { + this.clearParam(this.#scopeParamName) + return this + } + + /** + * Merge to existing scopes + */ + mergeScopes(scopes: LiteralStringUnion[]): this { + if (typeof this.#scopesTransformer === 'function') { + scopes = this.#scopesTransformer(scopes) + } + + const params = this.getParams() + const mergedScopes = (params[this.#scopeParamName] || []).concat(scopes) + this.scopes(mergedScopes) + + return this + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..cfcc7fc --- /dev/null +++ b/src/types.ts @@ -0,0 +1,637 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { HttpContext } from '@adonisjs/core/http' +import { + Oauth2AccessToken, + Oauth1RequestToken, + Oauth1AccessToken, + Oauth1ClientConfig, + Oauth2ClientConfig, + ApiRequestContract, + RedirectRequestContract as ClientRequestContract, +} from '@poppinss/oauth-client/types' + +export type { Oauth2AccessToken } +export type { Oauth1AccessToken } +export type { Oauth1RequestToken } +export type { ApiRequestContract } +export type { Oauth2ClientConfig as Oauth2DriverConfig } +export type { Oauth1ClientConfig as Oauth1DriverConfig } + +/** + * Issue: https://github.com/Microsoft/TypeScript/issues/29729 + * Solution: https://github.com/sindresorhus/type-fest/blob/main/source/literal-union.d.ts + */ +export type LiteralStringUnion = LiteralType | (string & { _?: never }) + +/** + * Extension of oauth-client redirect request with support + * for defining scopes as first class citizen + */ +export interface RedirectRequestContract + extends ClientRequestContract { + /** + * Define a callback to transform scopes before they are defined + * as a param + */ + transformScopes(callback: (scopes: LiteralStringUnion[]) => string[]): this + + /** + * Define the scopes for authorization + */ + scopes(scopes: LiteralStringUnion[]): this + + /** + * Merge to existing pre-defined scopes + */ + mergeScopes(scopes: LiteralStringUnion[]): this + + /** + * Clear existing scopes + */ + clearScopes(): this +} + +/** + * The user fetched from the oauth provider. + */ +export interface AllyUserContract { + id: string + nickName: string + name: string + email: string | null + emailVerificationState: 'verified' | 'unverified' | 'unsupported' + avatarUrl: string | null + token: Token + original: any +} + +/** + * Every driver should implement this contract + */ +export interface AllyDriverContract< + Token extends Oauth2AccessToken | Oauth1AccessToken, + Scopes extends string, +> { + version: 'oauth1' | 'oauth2' + + /** + * Perform stateless authentication. Only applicable for Oauth2 clients + */ + stateless(): this + + /** + * Redirect user for authorization + */ + redirect(callback?: (request: RedirectRequestContract) => void): Promise + + /** + * Get redirect url. You must manage the state yourself when redirecting + * manually + */ + redirectUrl(callback?: (request: RedirectRequestContract) => void): Promise + + /** + * Find if the current request has authorization code or oauth token + */ + hasCode(): boolean + + /** + * Get the current request authorization code or oauth token. Returns + * null if there no code + */ + getCode(): string | null + + /** + * Find if the current error code is for access denied + */ + accessDenied(): boolean + + /** + * Find if there is a state mismatch + */ + stateMisMatch(): boolean + + /** + * Find if there is an error post redirect + */ + hasError(): boolean + + /** + * Get the post redirect error + */ + getError(): string | null + + /** + * Get access token + */ + accessToken(callback?: (request: ApiRequestContract) => void): Promise + + /** + * Returns details for the authorized user + */ + user(callback?: (request: ApiRequestContract) => void): Promise> + + /** + * Finds the user by access token. Applicable with "Oauth2" only + */ + userFromToken( + token: string, + callback?: (request: ApiRequestContract) => void + ): Promise> + + /** + * Finds the user by access token. Applicable with "Oauth1" only + */ + userFromTokenAndSecret( + token: string, + secret: string, + callback?: (request: ApiRequestContract) => void + ): Promise> +} + +/** + * ---------------------------------------- + * Discord driver + * ---------------------------------------- + */ + +/** + * Available discord scopes + * https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes + */ +export type DiscordScopes = + | 'activities.read' + | 'activities.write' + | 'applications.builds.read' + | 'applications.builds.upload' + | 'applications.commands' + | 'applications.commands.update' + | 'applications.entitlements' + | 'applications.store.update' + | 'bot' + | 'connections' + | 'email' + | 'gdm.join' + | 'guilds' + | 'guilds.join' + | 'identify' + | 'messages.read' + | 'relationships.read' + | 'rpc' + | 'rpc.activities.write' + | 'rpc.notifications.read' + | 'rpc.voice.read' + | 'rpc.voice.write' + | 'webhook.incoming' + +/** + * Shape of the Discord access token + */ +export type DiscordToken = { + token: string + type: string + scope: string + expiresIn: number + expiresAt: Exclude + refreshToken: string +} + +/** + * Extra options available for Discord + */ +export type DiscordDriverConfig = Oauth2ClientConfig & { + driver: 'discord' + userInfoUrl?: string + scopes?: LiteralStringUnion[] + prompt?: 'consent' | 'none' + guildId?: `${bigint}` // a snowflake + disableGuildSelect?: boolean + permissions?: number +} + +export interface DiscordDriverContract extends AllyDriverContract { + version: 'oauth2' +} + +/** + * ---------------------------------------- + * Github driver + * ---------------------------------------- + */ + +/** + * Available github scopes + * https://docs.github.com/en/free-pro-team@latest/developers/apps/scopes-for-oauth-apps#available-scopes + */ +export type GithubScopes = + | 'repo' + | 'repo:status' + | 'repo_deployment' + | 'public_repo' + | 'repo:invite' + | 'security_events' + | 'admin:repo_hook' + | 'write:repo_hook' + | 'read:repo_hook' + | 'admin:org' + | 'write:org' + | 'read:org' + | 'admin:public_key' + | 'write:public_key' + | 'read:public_key' + | 'admin:org_hook' + | 'gist' + | 'notifications' + | 'user' + | 'read:user' + | 'user:email' + | 'user:follow' + | 'delete_repo' + | 'write:discussion' + | 'read:discussion' + | 'write:packages' + | 'read:packages' + | 'delete:packages' + | 'admin:gpg_key' + | 'write:gpg_key' + | 'read:gpg_key' + | 'workflow' + +/** + * Shape of the Github access token + */ +export type GithubToken = { + token: string + type: string + scope: string +} + +/** + * Extra options available for Github + */ +export type GithubDriverConfig = Oauth2ClientConfig & { + driver: 'github' + login?: string + scopes?: LiteralStringUnion[] + allowSignup?: boolean + userInfoUrl?: string + userEmailUrl?: string +} + +export interface GithubDriverContract extends AllyDriverContract { + version: 'oauth2' +} + +/** + * ---------------------------------------- + * Twitter driver + * ---------------------------------------- + */ + +/** + * Shape of the twitter token + */ +export type TwitterToken = { + token: string + secret: string + userId: string + screenName: string +} + +/** + * Extra options available for twitter + */ +export type TwitterDriverConfig = Oauth1ClientConfig & { + driver: 'twitter' + userInfoUrl?: string +} + +export interface TwitterDriverContract extends AllyDriverContract { + version: 'oauth1' +} + +/** + * ---------------------------------------- + * Google driver + * ---------------------------------------- + */ + +/** + * Most popular google scopes. You can find rest of them on the following link + * https://developers.google.com/identity/protocols/oauth2/scopes + */ +export type GoogleScopes = + | 'userinfo.email' + | 'userinfo.profile' + | 'openid' + | 'contacts' + | 'contacts.other.readonly' + | 'contacts.readonly' + | 'directory.readonly' + | 'user.addresses.read' + | 'user.birthday.read' + | 'user.emails.read' + | 'user.gender.read' + | 'user.organization.read' + | 'user.phonenumbers.read' + | 'analytics' + | 'analytics.readonly' + | 'documents' + | 'documents.readonly' + | 'forms' + | 'forms.currentonly' + | 'groups' + | 'spreadsheets' + | 'calendar' + | 'calendar.events' + | 'calendar.events.readonly' + | 'calendar.readonly' + | 'calendar.settings.readonly' + | 'drive' + | 'drive.appdata' + | 'drive.file' + | 'drive.metadata' + | 'drive.metadata.readonly' + | 'drive.photos.readonly' + | 'drive.readonly' + | 'drive.scripts' + +/** + * Shape of the Google access token + */ +export type GoogleToken = Oauth2AccessToken & { + /** + * @deprecated + * Use `idToken` instead + */ + id_token: string + idToken: string + grantedScopes: string[] +} + +/** + * Config accepted by the google driver. Most of the options can be + * overwritten at runtime + * https://developers.google.com/identity/protocols/oauth2/openid-connect#re-consent + */ +export type GoogleDriverConfig = Oauth2ClientConfig & { + driver: 'google' + userInfoUrl?: string + + /** + * Can be configured at runtime + */ + scopes?: LiteralStringUnion[] + prompt?: 'none' | 'consent' | 'select_account' + accessType?: 'online' | 'offline' + hostedDomain?: string + display?: 'page' | 'popup' | 'touch' | 'wrap' +} + +export interface GoogleDriverContract extends AllyDriverContract { + version: 'oauth2' +} + +/** + * ---------------------------------------- + * LinkedIn driver + * ---------------------------------------- + */ + +/** + * A list of LinkedIn scopes. You can find the scopes available + * to your app from the LinkedIn dasbhoard. + * https://www.linkedin.com/developers/apps//auth + */ +export type LinkedInScopes = + | 'r_emailaddress' + | 'r_liteprofile' + | 'w_member_social' + | 'r_fullprofile' + | 'r_basicprofile_app' + | 'r_primarycontact' + | 'rw_organization_admin' + +/** + * Shape of the Linked access token + */ +export type LinkedInToken = { + token: string + type: string + expiresIn: number + expiresAt: Exclude +} + +/** + * Config accepted by the linkedin driver. Most of the options can be + * overwritten at runtime + * https://docs.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow?context=linkedin%2Fcontext&tabs=HTTPS#step-2-request-an-authorization-code + */ +export type LinkedInDriverConfig = Oauth2ClientConfig & { + driver: 'linkedin' + userInfoUrl?: string + userEmailUrl?: string + + /** + * Can be configured at runtime + */ + scopes?: LiteralStringUnion[] +} + +export interface LinkedInDriverContract extends AllyDriverContract { + version: 'oauth2' +} + +/** + * ---------------------------------------- + * Facebook driver + * ---------------------------------------- + */ + +/** + * Most popular facebook scopes. You can find rest of them on the following link + * https://developers.facebook.com/docs/permissions/reference/ + */ +export type FacebookScopes = + | 'ads_management' + | 'ads_read' + | 'attribution_read' + | 'business_management' + | 'catalog_management' + | 'email' + | 'groups_access_member_info' + | 'leads_retrieval' + | 'pages_events' + | 'pages_manage_ads' + | 'pages_manage_cta' + | 'pages_manage_instant_articles' + | 'pages_manage_engagement' + | 'pages_manage_metadata' + | 'pages_manage_posts' + | 'pages_messaging' + | 'pages_read_engagement' + | 'pages_read_user_content' + | 'pages_show_list' + | 'pages_user_gender' + | 'pages_user_locale' + | 'pages_user_timezone' + | 'public_profile' + | 'publish_to_groups' + | 'publish_video' + | 'read_insights' + | 'user_age_range' + | 'user_birthday' + | 'user_friends' + | 'user_gender' + | 'user_hometown' + | 'user_likes' + | 'user_link' + | 'user_location' + | 'user_photos' + | 'user_posts' + | 'user_videos' + +/** + * Most used user profile fields. + * For more visit https://developers.facebook.com/docs/graph-api/reference/user + */ +export type FacebookProfileFields = + | 'id' + | 'first_name' + | 'last_name' + | 'middle_name' + | 'name' + | 'name_format' + | 'picture' + | 'short_name' + | 'verified' + | 'birthday' + | 'email' + | 'gender' + | 'link' + +/** + * Shape of the Facebook access token + */ +export type FacebookToken = { + token: string + type: string + expiresIn: number + expiresAt: Exclude +} + +/** + * Config accepted by the facebook driver. Most of the options can be + * overwritten at runtime + * https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow + */ +export type FacebookDriverConfig = Oauth2ClientConfig & { + driver: 'facebook' + userInfoUrl?: string + + /** + * Can be configured at runtime + */ + scopes?: LiteralStringUnion[] + userFields?: LiteralStringUnion[] + display?: string + authType?: string +} + +export interface FacebookDriverContract extends AllyDriverContract { + version: 'oauth2' +} + +/** + * ---------------------------------------- + * Spotify driver + * ---------------------------------------- + */ + +/** + * Available spotify scopes + * https://developer.spotify.com/documentation/general/guides/scopes/ + */ +export type SpotifyScopes = + | 'ugc-image-upload' + | 'user-read-recently-played' + | 'user-top-read' + | 'user-read-playback-position' + | 'user-read-playback-state' + | 'user-modify-playback-state' + | 'user-read-currently-playing' + | 'app-remote-control' + | 'streaming' + | 'playlist-modify-public' + | 'playlist-modify-private' + | 'playlist-read-private' + | 'playlist-read-collaborative' + | 'user-follow-modify' + | 'user-follow-read' + | 'user-library-modify' + | 'user-library-read' + | 'user-read-email' + | 'user-read-private' + +/** + * Shape of the Spotify access token + */ +export type SpotifyToken = { + token: string + type: string + refreshToken: string + expiresIn: number + expiresAt: Exclude +} + +/** + * Extra options available for Spotify + */ +export type SpotifyDriverConfig = Oauth2ClientConfig & { + driver: 'spotify' + scopes?: LiteralStringUnion[] + showDialog?: boolean +} + +export interface SpotifyDriverContract extends AllyDriverContract { + version: 'oauth2' +} + +/** + * END OF DRIVERS + */ + +/** + * List of known ally drivers. The list can be extended using + * declaration merging + */ +export interface AllyDriversList { + discord: (config: DiscordDriverConfig, ctx: HttpContext) => DiscordDriverContract + facebook: (config: FacebookDriverConfig, ctx: HttpContext) => FacebookDriverContract + github: (config: GithubDriverConfig, ctx: HttpContext) => GithubDriverContract + google: (config: GoogleDriverConfig, ctx: HttpContext) => GoogleDriverContract + linkedin: (config: LinkedInDriverConfig, ctx: HttpContext) => LinkedInDriverContract + spotify: (config: SpotifyDriverConfig, ctx: HttpContext) => SpotifyDriverContract + twitter: (config: TwitterDriverConfig, ctx: HttpContext) => TwitterDriverContract +} + +/** + * The manager driver factory method is called by the AllyManager to create + * an instance of a driver during an HTTP request + */ +export type AllyManagerDriverFactory = (ctx: HttpContext) => AllyDriverContract + +/** + * Social providers are inferred inside the user application + * from the config file + */ +export interface SocialProviders {} +export type InferSocialProviders> = T diff --git a/standalone.ts b/standalone.ts deleted file mode 100644 index 34976e7..0000000 --- a/standalone.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -/** - * Exports required to create a custom driver - */ -export { HttpClient as ApiRequest } from '@poppinss/oauth-client' -export { OauthException } from './src/Exceptions' -export { Oauth2Driver } from './src/AbstractDrivers/Oauth2' -export { RedirectRequest } from './src/RedirectRequest' diff --git a/test-helpers/contracts.ts b/test-helpers/contracts.ts deleted file mode 100644 index 2cd1490..0000000 --- a/test-helpers/contracts.ts +++ /dev/null @@ -1,32 +0,0 @@ -declare module '@ioc:Adonis/Addons/Ally' { - interface SocialProviders { - discord: { - config: DiscordDriverConfig - implementation: DiscordDriverContract - } - github: { - config: GithubDriverConfig - implementation: GithubDriverContract - } - twitter: { - config: TwitterDriverConfig - implementation: TwitterDriverContract - } - linkedin: { - config: LinkedInDriverConfig - implementation: LinkedInDriverContract - } - google: { - config: GoogleDriverConfig - implementation: GoogleDriverContract - } - facebook: { - config: FacebookDriverConfig - implementation: FacebookDriverContract - } - spotify: { - config: SpotifyDriverConfig - implementation: SpotifyDriverContract - } - } -} diff --git a/test-helpers/index.ts b/test-helpers/index.ts deleted file mode 100644 index aef750e..0000000 --- a/test-helpers/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/core/build/standalone' - -export const fs = new Filesystem(join(__dirname, '__app')) - -export async function setup(setupProviders?: boolean) { - const application = new Application(fs.basePath, 'web', { - providers: ['@adonisjs/core', '../../providers/AllyProvider'], - }) - - await fs.add( - 'config/app.ts', - ` - export const profiler = { enabled: true } - export const appKey = 'averylongrandomsecretkey' - export const http = { - trustProxy: () => {}, - cookie: {} - } - ` - ) - - await application.setup() - - if (setupProviders) { - await application.registerProviders() - await application.bootProviders() - } - - return application -} diff --git a/test/ally-manager.spec.ts b/test/ally-manager.spec.ts deleted file mode 100644 index 8df7021..0000000 --- a/test/ally-manager.spec.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { Ally } from '../src/Ally' -import { AllyManager } from '../src/AllyManager' -import { GithubDriver } from '../src/Drivers/Github' - -import { setup, fs } from '../test-helpers' - -test.group('AllyManager', (group) => { - group.teardown(async () => { - await fs.cleanup() - }) - - test('make instance of a mapping', async ({ assert }) => { - const app = await setup(true) - const manager = new AllyManager(app, { - github: { - driver: 'github', - }, - }) - - const HttpContext = app.container.resolveBinding('Adonis/Core/HttpContext') - assert.instanceOf(manager.makeMapping(HttpContext.create('/', {}), 'github'), GithubDriver) - }) - - test('register provider as singleton', async ({ assert }) => { - const app = await setup(true) - assert.strictEqual( - app.container.resolveBinding('Adonis/Addons/Ally'), - app.container.resolveBinding('Adonis/Addons/Ally') - ) - }) - - test('add ally getter to http context', async ({ assert }) => { - const app = await setup(true) - const HttpContext = app.container.resolveBinding('Adonis/Core/HttpContext') - - assert.instanceOf(HttpContext.create('/', {}).ally, Ally) - }) - - test('extend ally manager to add custom drivers', async ({ assert }) => { - const app = await setup(true) - class MyCustomDriver {} - - const manager = new AllyManager(app, { - foo: { - driver: 'foo', - }, - }) - manager.extend('foo', () => { - return new MyCustomDriver() as any - }) - - const HttpContext = app.container.resolveBinding('Adonis/Core/HttpContext') - assert.instanceOf( - manager.makeMapping(HttpContext.create('/', {}), 'foo' as any), - MyCustomDriver - ) - }) -}) diff --git a/tsconfig.json b/tsconfig.json index 3c150aa..2039043 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,7 @@ { - "extends": "./node_modules/@adonisjs/mrm-preset/_tsconfig", + "extends": "@adonisjs/tsconfig/tsconfig.package.json", "compilerOptions": { - "types": [ - "@types/node", - "@adonisjs/core" - ], - "skipLibCheck": true + "rootDir": "./", + "outDir": "./build" } -} +} \ No newline at end of file From f092b8f2f1b7ff34108717a7ebdafc40bcfe264f Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 17 Jul 2023 12:18:08 +0530 Subject: [PATCH 02/37] style: update license doc block --- examples/bitbucket.ts | 2 +- examples/discord.ts | 2 +- examples/facebook.ts | 2 +- examples/github.ts | 2 +- examples/gitlab.ts | 2 +- examples/google.ts | 2 +- examples/linkedin.ts | 2 +- examples/microsoft.ts | 2 +- examples/spotify.ts | 2 +- examples/twitter.ts | 2 +- index.ts | 11 ++++++----- src/abstract_drivers/oauth1.ts | 2 +- src/defaults/config.ts | 2 +- src/drivers/discord.ts | 2 +- src/drivers/facebook.ts | 2 +- src/drivers/github.ts | 2 +- src/drivers/google.ts | 2 +- src/drivers/linked_in.ts | 2 +- src/drivers/spotify.ts | 2 +- src/drivers/twitter.ts | 2 +- 20 files changed, 25 insertions(+), 24 deletions(-) diff --git a/examples/bitbucket.ts b/examples/bitbucket.ts index 5aa39a9..d936114 100644 --- a/examples/bitbucket.ts +++ b/examples/bitbucket.ts @@ -1,7 +1,7 @@ // /* // * @adonisjs/ally // * -// * (c) Harminder Virk +// * (c) AdonisJS // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. diff --git a/examples/discord.ts b/examples/discord.ts index b95ac78..c74794c 100644 --- a/examples/discord.ts +++ b/examples/discord.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/examples/facebook.ts b/examples/facebook.ts index 31bd5a4..3d56e57 100644 --- a/examples/facebook.ts +++ b/examples/facebook.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/examples/github.ts b/examples/github.ts index 4d41282..58c6aa9 100644 --- a/examples/github.ts +++ b/examples/github.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/examples/gitlab.ts b/examples/gitlab.ts index 59785a0..5fa36c5 100644 --- a/examples/gitlab.ts +++ b/examples/gitlab.ts @@ -1,7 +1,7 @@ // /* // * @adonisjs/ally // * -// * (c) Harminder Virk +// * (c) AdonisJS // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. diff --git a/examples/google.ts b/examples/google.ts index 4825a24..84953dd 100644 --- a/examples/google.ts +++ b/examples/google.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/examples/linkedin.ts b/examples/linkedin.ts index d519887..1f80b84 100644 --- a/examples/linkedin.ts +++ b/examples/linkedin.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/examples/microsoft.ts b/examples/microsoft.ts index 55653e5..b52c79c 100644 --- a/examples/microsoft.ts +++ b/examples/microsoft.ts @@ -1,7 +1,7 @@ // /* // * @adonisjs/ally // * -// * (c) Harminder Virk +// * (c) AdonisJS // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. diff --git a/examples/spotify.ts b/examples/spotify.ts index ce6692a..39a6fe6 100644 --- a/examples/spotify.ts +++ b/examples/spotify.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/examples/twitter.ts b/examples/twitter.ts index 7efa5c8..6cbfcec 100644 --- a/examples/twitter.ts +++ b/examples/twitter.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/index.ts b/index.ts index 9f1a573..454ddc0 100644 --- a/index.ts +++ b/index.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -11,9 +11,10 @@ import './src/bindings/types.js' export { HttpClient as ApiRequest } from '@poppinss/oauth-client' -export { Oauth2Driver } from './src/abstract_drivers/oauth2.js' -export { Oauth1Driver } from './src/abstract_drivers/oauth1.js' -export { AllyManager } from './src/ally_manager.js' -export { default as driversList } from './src/drivers_collection.js' export * as errors from './src/exceptions.js' +export { AllyManager } from './src/ally_manager.js' +export { defineConfig } from './src/define_config.js' export { RedirectRequest } from './src/redirect_request.js' +export { Oauth1Driver } from './src/abstract_drivers/oauth1.js' +export { Oauth2Driver } from './src/abstract_drivers/oauth2.js' +export { default as driversList } from './src/drivers_collection.js' diff --git a/src/abstract_drivers/oauth1.ts b/src/abstract_drivers/oauth1.ts index 87d03ae..e3a7f57 100644 --- a/src/abstract_drivers/oauth1.ts +++ b/src/abstract_drivers/oauth1.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/src/defaults/config.ts b/src/defaults/config.ts index d441a65..76af456 100644 --- a/src/defaults/config.ts +++ b/src/defaults/config.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/src/drivers/discord.ts b/src/drivers/discord.ts index b2bc95a..4e88594 100644 --- a/src/drivers/discord.ts +++ b/src/drivers/discord.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/src/drivers/facebook.ts b/src/drivers/facebook.ts index 1534384..1ba6851 100644 --- a/src/drivers/facebook.ts +++ b/src/drivers/facebook.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/src/drivers/github.ts b/src/drivers/github.ts index 5d4c809..f6854ac 100644 --- a/src/drivers/github.ts +++ b/src/drivers/github.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/src/drivers/google.ts b/src/drivers/google.ts index 93b1091..1283547 100644 --- a/src/drivers/google.ts +++ b/src/drivers/google.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/src/drivers/linked_in.ts b/src/drivers/linked_in.ts index bd41079..c32a51b 100644 --- a/src/drivers/linked_in.ts +++ b/src/drivers/linked_in.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/src/drivers/spotify.ts b/src/drivers/spotify.ts index daa3865..b2c4841 100644 --- a/src/drivers/spotify.ts +++ b/src/drivers/spotify.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/src/drivers/twitter.ts b/src/drivers/twitter.ts index ce1e5d8..b8c03b9 100644 --- a/src/drivers/twitter.ts +++ b/src/drivers/twitter.ts @@ -1,7 +1,7 @@ /* * @adonisjs/ally * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. From 76e20adb624f7aafcf253293b327a32b0049607c Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 17 Jul 2023 12:29:26 +0530 Subject: [PATCH 03/37] refactor: setup stubs --- configure.ts | 21 ++++++++++ index.ts | 2 + package.json | 15 +++++--- stubs/config.stub | 7 ++++ stubs/main.ts | 12 ++++++ stubs/types.stub | 12 ++++++ templates/config/ally.txt | 44 --------------------- templates/config/partials/discord.txt | 11 ------ templates/config/partials/facebook.txt | 11 ------ templates/config/partials/github.txt | 11 ------ templates/config/partials/google.txt | 11 ------ templates/config/partials/linkedin.txt | 11 ------ templates/config/partials/spotify.txt | 12 ------ templates/config/partials/twitter.txt | 11 ------ templates/contracts/ally.txt | 53 -------------------------- 15 files changed, 64 insertions(+), 180 deletions(-) create mode 100644 configure.ts create mode 100644 stubs/config.stub create mode 100644 stubs/main.ts create mode 100644 stubs/types.stub delete mode 100644 templates/config/ally.txt delete mode 100644 templates/config/partials/discord.txt delete mode 100644 templates/config/partials/facebook.txt delete mode 100644 templates/config/partials/github.txt delete mode 100644 templates/config/partials/google.txt delete mode 100644 templates/config/partials/linkedin.txt delete mode 100644 templates/config/partials/spotify.txt delete mode 100644 templates/config/partials/twitter.txt delete mode 100644 templates/contracts/ally.txt diff --git a/configure.ts b/configure.ts new file mode 100644 index 0000000..567abf9 --- /dev/null +++ b/configure.ts @@ -0,0 +1,21 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import type Configure from '@adonisjs/core/commands/configure' + +/** + * Configures the package + */ +export async function configure(command: Configure) { + await command.publishStub('config.stub') + await command.publishStub('types.stub') + await command.updateRcFile((rcFile) => { + rcFile.addProvider('@adonisjs/ally/ally_provider') + }) +} diff --git a/index.ts b/index.ts index 454ddc0..7ef217e 100644 --- a/index.ts +++ b/index.ts @@ -18,3 +18,5 @@ export { RedirectRequest } from './src/redirect_request.js' export { Oauth1Driver } from './src/abstract_drivers/oauth1.js' export { Oauth2Driver } from './src/abstract_drivers/oauth2.js' export { default as driversList } from './src/drivers_collection.js' +export { stubsRoot } from './stubs/main.js' +export { configure } from './configure.js' diff --git a/package.json b/package.json index 7afae93..4e40970 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,17 @@ "main": "build/index.js", "files": [ "build/src", + "build/stubs", + "build/providers", "build/index.d.ts", - "build/index.js" + "build/index.js", + "build/configure.d.ts", + "build/configure.js" ], "exports": { ".": "./build/index.js", - "./types": "./build/src/types.js" + "./types": "./build/src/types.js", + "./ally_provider": "./build/providers/ally_provider.js" }, "engines": { "node": ">=18.16.0" @@ -21,10 +26,10 @@ "test": "c8 npm run quick:test", "clean": "del-cli build", "typecheck": "tsc --noEmit", - "start": "node -r @adonisjs/assembler/build/register examples/app.ts", - "copyfiles": "copyfiles \"templates/**/*.txt\" \"instructions.md\" build", + "start": "node --loader=ts-node/esm examples/app.ts", + "copy:templates": "copyfiles \"stubs/**/*.stub\" build", "compile": "npm run lint && npm run clean && tsc", - "build": "npm run compile && npm run copyfiles", + "build": "npm run compile && npm run copy:templates", "prepublishOnly": "npm run build", "lint": "eslint . --ext=.ts", "format": "prettier --write .", diff --git a/stubs/config.stub b/stubs/config.stub new file mode 100644 index 0000000..70cda38 --- /dev/null +++ b/stubs/config.stub @@ -0,0 +1,7 @@ +--- +to: {{ app.configPath('ally.ts') }} +--- +import { defineConfig } from '@adonisjs/ally' +import env from '#start/env' + +export default defineConfig({}) diff --git a/stubs/main.ts b/stubs/main.ts new file mode 100644 index 0000000..2fc8529 --- /dev/null +++ b/stubs/main.ts @@ -0,0 +1,12 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { getDirname } from '@poppinss/utils' + +export const stubsRoot = getDirname(import.meta.url) diff --git a/stubs/types.stub b/stubs/types.stub new file mode 100644 index 0000000..258f039 --- /dev/null +++ b/stubs/types.stub @@ -0,0 +1,12 @@ +--- +to: {{ app.contractsPath('ally.ts') }} +--- +import allyConfig from '#config/ally' + +declare module '@adonisjs/ally/types' { + /** + * Here we get a list of social providers you have configured inside + * the config/ally.ts file. + */ + interface SocialProviders extends InferSocialProviders {} +} diff --git a/templates/config/ally.txt b/templates/config/ally.txt deleted file mode 100644 index fbad703..0000000 --- a/templates/config/ally.txt +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Config source: https://git.io/JOdi5 - * - * Feel free to let us know via PR, if you find something broken in this config - * file. - */ - -import Env from '@ioc:Adonis/Core/Env' -import { AllyConfig } from '@ioc:Adonis/Addons/Ally' - -/* -|-------------------------------------------------------------------------- -| Ally Config -|-------------------------------------------------------------------------- -| -| The `AllyConfig` relies on the `SocialProviders` interface which is -| defined inside `contracts/ally.ts` file. -| -*/ -const allyConfig: AllyConfig = { -{{#providers.github}} -{{> github_provider}} -{{/providers.github}} -{{#providers.google}} -{{> google_provider}} -{{/providers.google}} -{{#providers.twitter}} -{{> twitter_provider}} -{{/providers.twitter}} -{{#providers.discord}} -{{> discord_provider}} -{{/providers.discord}} -{{#providers.linkedin}} -{{> linkedin_provider}} -{{/providers.linkedin}} -{{#providers.facebook}} -{{> facebook_provider}} -{{/providers.facebook}} -{{#providers.spotify}} -{{> spotify_provider}} -{{/providers.spotify}} -} - -export default allyConfig diff --git a/templates/config/partials/discord.txt b/templates/config/partials/discord.txt deleted file mode 100644 index d88dcdb..0000000 --- a/templates/config/partials/discord.txt +++ /dev/null @@ -1,11 +0,0 @@ - /* - |-------------------------------------------------------------------------- - | Discord driver - |-------------------------------------------------------------------------- - */ - discord: { - driver: 'discord', - clientId: Env.get('{{ envVars.discord.clientId }}'), - clientSecret: Env.get('{{ envVars.discord.clientSecret }}'), - callbackUrl: 'http://localhost:3333/discord/callback', - }, diff --git a/templates/config/partials/facebook.txt b/templates/config/partials/facebook.txt deleted file mode 100644 index 34981ee..0000000 --- a/templates/config/partials/facebook.txt +++ /dev/null @@ -1,11 +0,0 @@ - /* - |-------------------------------------------------------------------------- - | Facebook driver - |-------------------------------------------------------------------------- - */ - facebook: { - driver: 'facebook', - clientId: Env.get('{{ envVars.facebook.clientId }}'), - clientSecret: Env.get('{{ envVars.facebook.clientSecret }}'), - callbackUrl: 'http://localhost:3333/facebook/callback', - }, diff --git a/templates/config/partials/github.txt b/templates/config/partials/github.txt deleted file mode 100644 index e4f2ed4..0000000 --- a/templates/config/partials/github.txt +++ /dev/null @@ -1,11 +0,0 @@ - /* - |-------------------------------------------------------------------------- - | Github driver - |-------------------------------------------------------------------------- - */ - github: { - driver: 'github', - clientId: Env.get('{{ envVars.github.clientId }}'), - clientSecret: Env.get('{{ envVars.github.clientSecret }}'), - callbackUrl: 'http://localhost:3333/github/callback', - }, diff --git a/templates/config/partials/google.txt b/templates/config/partials/google.txt deleted file mode 100644 index a2e6630..0000000 --- a/templates/config/partials/google.txt +++ /dev/null @@ -1,11 +0,0 @@ - /* - |-------------------------------------------------------------------------- - | Google driver - |-------------------------------------------------------------------------- - */ - google: { - driver: 'google', - clientId: Env.get('{{ envVars.google.clientId }}'), - clientSecret: Env.get('{{ envVars.google.clientSecret }}'), - callbackUrl: 'http://localhost:3333/google/callback', - }, diff --git a/templates/config/partials/linkedin.txt b/templates/config/partials/linkedin.txt deleted file mode 100644 index 67aabc9..0000000 --- a/templates/config/partials/linkedin.txt +++ /dev/null @@ -1,11 +0,0 @@ - /* - |-------------------------------------------------------------------------- - | LinkedIn driver - |-------------------------------------------------------------------------- - */ - linkedin: { - driver: 'linkedin', - clientId: Env.get('{{ envVars.linkedin.clientId }}'), - clientSecret: Env.get('{{ envVars.linkedin.clientSecret }}'), - callbackUrl: 'http://localhost:3333/linkedin/callback', - }, diff --git a/templates/config/partials/spotify.txt b/templates/config/partials/spotify.txt deleted file mode 100644 index 08758f0..0000000 --- a/templates/config/partials/spotify.txt +++ /dev/null @@ -1,12 +0,0 @@ - /* - |-------------------------------------------------------------------------- - | Spotify driver - |-------------------------------------------------------------------------- - */ - spotify: { - driver: 'spotify', - clientId: Env.get('{{ envVars.spotify.clientId }}'), - clientSecret: Env.get('{{ envVars.spotify.clientSecret }}'), - callbackUrl: 'http://localhost:3333/spotify/callback', - }, - diff --git a/templates/config/partials/twitter.txt b/templates/config/partials/twitter.txt deleted file mode 100644 index bb849e6..0000000 --- a/templates/config/partials/twitter.txt +++ /dev/null @@ -1,11 +0,0 @@ - /* - |-------------------------------------------------------------------------- - | Twitter driver - |-------------------------------------------------------------------------- - */ - twitter: { - driver: 'twitter', - clientId: Env.get('{{ envVars.twitter.clientId }}'), - clientSecret: Env.get('{{ envVars.twitter.clientSecret }}'), - callbackUrl: 'http://localhost:3333/twitter/callback', - }, diff --git a/templates/contracts/ally.txt b/templates/contracts/ally.txt deleted file mode 100644 index b54a3c3..0000000 --- a/templates/contracts/ally.txt +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Contract source: https://git.io/JOdiQ - * - * Feel free to let us know via PR, if you find something broken in this contract - * file. - */ - -declare module '@ioc:Adonis/Addons/Ally' { - interface SocialProviders { - {{#providers.github}} - github: { - config: GithubDriverConfig - implementation: GithubDriverContract - } - {{/providers.github}} - {{#providers.google}} - google: { - config: GoogleDriverConfig - implementation: GoogleDriverContract - } - {{/providers.google}} - {{#providers.twitter}} - twitter: { - config: TwitterDriverConfig - implementation: TwitterDriverContract - } - {{/providers.twitter}} - {{#providers.discord}} - discord: { - config: DiscordDriverConfig - implementation: DiscordDriverContract - } - {{/providers.discord}} - {{#providers.linkedin}} - linkedin: { - config: LinkedInDriverConfig - implementation: LinkedInDriverContract - } - {{/providers.linkedin}} - {{#providers.facebook}} - facebook: { - config: FacebookDriverConfig - implementation: FacebookDriverContract - } - {{/providers.facebook}} - {{#providers.spotify}} - spotify: { - config: SpotifyDriverConfig - implementation: SpotifyDriverContract - } - {{/providers.spotify}} - } -} From 72f7ebc5cac997daa1f3de0d567e4a09ca607a78 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 17 Jul 2023 12:31:13 +0530 Subject: [PATCH 04/37] docs: update README --- README.md | 67 +++++++++++++++++++++---------------------------------- 1 file changed, 25 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 02fdcd6..36f0e81 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,36 @@ -
- -
+# @adonisjs/ally
-
-

Social Authentication

-

Social authentication provider for AdonisJS. Supports Github, Google, Twitter, Facebook, Discord, Spotify, and LinkedIn.

-
+[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] [![snyk-image]][snyk-url] -
+## Introduction +Social authentication provider for AdonisJS. Supports *Github*, Google, Twitter, Facebook, Discord, Spotify, and LinkedIn. -
- -[![gh-workflow-image]][gh-workflow-url] [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url] [![synk-image]][synk-url] - -
- - - -
- Built with ❤︎ by Harminder Virk -
- -[gh-workflow-image]: https://img.shields.io/github/workflow/status/adonisjs/ally/test?style=for-the-badge -[gh-workflow-url]: https://github.com/adonisjs/ally/actions/workflows/test.yml "Github action" +## Official Documentation +The documentation is available on the [AdonisJS website](https://docs.adonisjs.com/guides/auth/social) -[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript -[typescript-url]: "typescript" +## Contributing +One of the primary goals of AdonisJS is to have a vibrant community of users and contributors who believes in the principles of the framework. + +We encourage you to read the [contribution guide](https://github.com/adonisjs/.github/blob/main/docs/CONTRIBUTING.md) before contributing to the framework. + +## Code of Conduct +In order to ensure that the AdonisJS community is welcoming to all, please review and abide by the [Code of Conduct](https://github.com/adonisjs/.github/blob/main/docs/CODE_OF_CONDUCT.md). + +## License +AdonisJS ally is open-sourced software licensed under the [MIT license](LICENSE.md). + +[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/adonisjs/ally/test.yml?style=for-the-badge +[gh-workflow-url]: https://github.com/adonisjs/ally/actions/workflows/test.yml "Github action" [npm-image]: https://img.shields.io/npm/v/@adonisjs/ally/latest.svg?style=for-the-badge&logo=npm [npm-url]: https://www.npmjs.com/package/@adonisjs/ally/v/latest "npm" -[license-image]: https://img.shields.io/npm/l/@adonisjs/ally?color=blueviolet&style=for-the-badge -[license-url]: LICENSE.md "license" +[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript + +[license-url]: LICENSE.md +[license-image]: https://img.shields.io/github/license/adonisjs/ally?style=for-the-badge -[synk-image]: https://img.shields.io/snyk/vulnerabilities/github/adonisjs/ally?label=Synk%20Vulnerabilities&style=for-the-badge -[synk-url]: https://snyk.io/test/github/adonisjs/ally?targetFile=package.json "synk" +[snyk-image]: https://img.shields.io/snyk/vulnerabilities/github/adonisjs/ally?label=Snyk%20Vulnerabilities&style=for-the-badge +[snyk-url]: https://snyk.io/test/github/adonisjs/ally?targetFile=package.json "snyk" From dee47523ba7d10cbe38612603a533bb950a8eddc Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 17 Jul 2023 14:18:03 +0530 Subject: [PATCH 05/37] test: add tests --- bin/test.ts | 6 +- package.json | 9 ++- src/ally_manager.ts | 5 +- src/redirect_request.ts | 31 +++++---- src/types.ts | 7 -- tests/ally_manager.spec.ts | 47 ++++++++++++++ tests/configure.spec.ts | 51 +++++++++++++++ tests/define_config.spec.ts | 37 +++++++++++ tests/drivers_collection.spec.ts | 107 +++++++++++++++++++++++++++++++ tests/redirect_request.spec.ts | 55 ++++++++++++++++ 10 files changed, 332 insertions(+), 23 deletions(-) create mode 100644 tests/ally_manager.spec.ts create mode 100644 tests/configure.spec.ts create mode 100644 tests/define_config.spec.ts create mode 100644 tests/drivers_collection.spec.ts create mode 100644 tests/redirect_request.spec.ts diff --git a/bin/test.ts b/bin/test.ts index 1b83905..3ee1fcd 100644 --- a/bin/test.ts +++ b/bin/test.ts @@ -1,4 +1,6 @@ import { assert } from '@japa/assert' +import { expectTypeOf } from '@japa/expect-type' +import { fileSystem } from '@japa/file-system' import { processCLIArgs, configure, run } from '@japa/runner' /* @@ -16,8 +18,8 @@ import { processCLIArgs, configure, run } from '@japa/runner' */ processCLIArgs(process.argv.slice(2)) configure({ - files: ['test/**/*.spec.ts'], - plugins: [assert()], + files: ['tests/**/*.spec.ts'], + plugins: [assert(), expectTypeOf(), fileSystem()], }) /* diff --git a/package.json b/package.json index 4e40970..e38dc67 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "release": "np", "version": "npm run build", "sync-labels": "github-label-sync --labels .github/labels.json adonisjs/ally", - "quick:test": "node --loader=ts-node/esm bin/test.ts" + "quick:test": "node --enable-source-maps --loader=ts-node/esm bin/test.ts" }, "keywords": [ "adonis", @@ -54,6 +54,7 @@ "@commitlint/cli": "^17.6.6", "@commitlint/config-conventional": "^17.6.6", "@japa/assert": "^2.0.0-1", + "@japa/expect-type": "^2.0.0-0", "@japa/runner": "^3.0.0-6", "@swc/core": "^1.3.69", "@types/node": "^20.4.2", @@ -71,6 +72,7 @@ "typescript": "^5.1.6" }, "dependencies": { + "@japa/file-system": "^2.0.0-1", "@poppinss/oauth-client": "^5.1.0-3", "@poppinss/utils": "^6.5.0-3" }, @@ -107,7 +109,10 @@ "html" ], "exclude": [ - "tests/**" + "tests/**", + "src/drivers/**", + "src/abstract_drivers/**", + "stubs/**" ] } } diff --git a/src/ally_manager.ts b/src/ally_manager.ts index fa8ae49..f40b5c0 100644 --- a/src/ally_manager.ts +++ b/src/ally_manager.ts @@ -47,6 +47,9 @@ export class AllyManager + const driverInstance = driver(this.#ctx) as ReturnType + this.#driversCache.set(provider, driverInstance) + + return driverInstance } } diff --git a/src/redirect_request.ts b/src/redirect_request.ts index 78ee669..0e07cfb 100644 --- a/src/redirect_request.ts +++ b/src/redirect_request.ts @@ -24,6 +24,10 @@ export class RedirectRequest extends UrlBuilder { this.#scopeSeparator = scopeSeparator } + /** + * Register a custom function to transform scopes. Exposed for drivers + * to implement. + */ transformScopes(callback: (scopes: LiteralStringUnion[]) => string[]): this { this.#scopesTransformer = callback return this @@ -41,14 +45,6 @@ export class RedirectRequest extends UrlBuilder { return this } - /** - * Clear existing scopes - */ - clearScopes(): this { - this.clearParam(this.#scopeParamName) - return this - } - /** * Merge to existing scopes */ @@ -57,10 +53,23 @@ export class RedirectRequest extends UrlBuilder { scopes = this.#scopesTransformer(scopes) } - const params = this.getParams() - const mergedScopes = (params[this.#scopeParamName] || []).concat(scopes) - this.scopes(mergedScopes) + const existingScopes = this.getParams()[this.#scopeParamName] + const scopesString = scopes.join(this.#scopeSeparator) + if (!existingScopes) { + this.param(this.#scopeParamName, scopesString) + return this + } + + this.param(this.#scopeParamName, `${existingScopes}${this.#scopeSeparator}${scopesString}`) + return this + } + + /** + * Clear existing scopes + */ + clearScopes(): this { + this.clearParam(this.#scopeParamName) return this } } diff --git a/src/types.ts b/src/types.ts index cfcc7fc..9849f88 100644 --- a/src/types.ts +++ b/src/types.ts @@ -208,7 +208,6 @@ export type DiscordToken = { * Extra options available for Discord */ export type DiscordDriverConfig = Oauth2ClientConfig & { - driver: 'discord' userInfoUrl?: string scopes?: LiteralStringUnion[] prompt?: 'consent' | 'none' @@ -278,7 +277,6 @@ export type GithubToken = { * Extra options available for Github */ export type GithubDriverConfig = Oauth2ClientConfig & { - driver: 'github' login?: string scopes?: LiteralStringUnion[] allowSignup?: boolean @@ -310,7 +308,6 @@ export type TwitterToken = { * Extra options available for twitter */ export type TwitterDriverConfig = Oauth1ClientConfig & { - driver: 'twitter' userInfoUrl?: string } @@ -383,7 +380,6 @@ export type GoogleToken = Oauth2AccessToken & { * https://developers.google.com/identity/protocols/oauth2/openid-connect#re-consent */ export type GoogleDriverConfig = Oauth2ClientConfig & { - driver: 'google' userInfoUrl?: string /** @@ -436,7 +432,6 @@ export type LinkedInToken = { * https://docs.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow?context=linkedin%2Fcontext&tabs=HTTPS#step-2-request-an-authorization-code */ export type LinkedInDriverConfig = Oauth2ClientConfig & { - driver: 'linkedin' userInfoUrl?: string userEmailUrl?: string @@ -534,7 +529,6 @@ export type FacebookToken = { * https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow */ export type FacebookDriverConfig = Oauth2ClientConfig & { - driver: 'facebook' userInfoUrl?: string /** @@ -596,7 +590,6 @@ export type SpotifyToken = { * Extra options available for Spotify */ export type SpotifyDriverConfig = Oauth2ClientConfig & { - driver: 'spotify' scopes?: LiteralStringUnion[] showDialog?: boolean } diff --git a/tests/ally_manager.spec.ts b/tests/ally_manager.spec.ts new file mode 100644 index 0000000..2e95920 --- /dev/null +++ b/tests/ally_manager.spec.ts @@ -0,0 +1,47 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { test } from '@japa/runner' +import { HttpContextFactory } from '@adonisjs/core/factories/http' + +import { AllyManager } from '../src/ally_manager.js' +import { GithubDriver } from '../src/drivers/github.js' + +test.group('Ally manager', () => { + test('create an instance of a driver', ({ assert, expectTypeOf }) => { + const ctx = new HttpContextFactory().create() + + const ally = new AllyManager( + { + github: ($ctx) => { + return new GithubDriver($ctx, { + clientId: '', + clientSecret: '', + callbackUrl: '', + }) + }, + }, + ctx + ) + + assert.instanceOf(ally.use('github'), GithubDriver) + assert.strictEqual(ally.use('github'), ally.use('github')) + expectTypeOf(ally.use).parameters.toEqualTypeOf<['github']>() + expectTypeOf(ally.use('github')).toMatchTypeOf() + }) + + test('throw error when making an unknown driver', () => { + const ctx = new HttpContextFactory().create() + + const ally = new AllyManager({}, ctx) + ;(ally.use as any)('github') + }).throws( + 'Unknown ally provider "github". Make sure it is registered inside the config/ally.ts file' + ) +}) diff --git a/tests/configure.spec.ts b/tests/configure.spec.ts new file mode 100644 index 0000000..a9c0b9f --- /dev/null +++ b/tests/configure.spec.ts @@ -0,0 +1,51 @@ +/* + * @adonisjs/static + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { test } from '@japa/runner' +import { fileURLToPath } from 'node:url' +import { IgnitorFactory } from '@adonisjs/core/factories' +import Configure from '@adonisjs/core/commands/configure' + +const BASE_URL = new URL('./tmp/', import.meta.url) + +test.group('Configure', (group) => { + group.each.setup(({ context }) => { + context.fs.baseUrl = BASE_URL + context.fs.basePath = fileURLToPath(BASE_URL) + }) + + test('create config file and register provider', async ({ assert }) => { + const ignitor = new IgnitorFactory() + .withCoreProviders() + .withCoreConfig() + .create(BASE_URL, { + importer: (filePath) => { + if (filePath.startsWith('./') || filePath.startsWith('../')) { + return import(new URL(filePath, BASE_URL).href) + } + + return import(filePath) + }, + }) + + const app = ignitor.createApp('web') + await app.init() + await app.boot() + + const ace = await app.container.make('ace') + const command = await ace.create(Configure, ['../../index.js']) + await command.exec() + + await assert.fileExists('config/ally.ts') + await assert.fileExists('contracts/ally.ts') + await assert.fileExists('.adonisrc.json') + await assert.fileContains('.adonisrc.json', '@adonisjs/ally/ally_provider') + await assert.fileContains('config/ally.ts', 'defineConfig') + }) +}) diff --git a/tests/define_config.spec.ts b/tests/define_config.spec.ts new file mode 100644 index 0000000..4e96f4a --- /dev/null +++ b/tests/define_config.spec.ts @@ -0,0 +1,37 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { test } from '@japa/runner' +import { HttpContextFactory } from '@adonisjs/core/factories/http' + +import { AllyManager, defineConfig } from '../index.js' +import { GithubDriver } from '../src/drivers/github.js' +import type { GithubDriverContract } from '../src/types.js' + +test.group('Define config', () => { + test('define manager config from user defined config', ({ assert, expectTypeOf }) => { + const managerConfig = defineConfig({ + github: { + driver: 'github', + clientId: '', + clientSecret: '', + callbackUrl: '', + scopes: ['admin:org'], + }, + }) + + const ctx = new HttpContextFactory().create() + const ally = new AllyManager(managerConfig, ctx) + + assert.instanceOf(ally.use('github'), GithubDriver) + assert.strictEqual(ally.use('github'), ally.use('github')) + expectTypeOf(ally.use).parameters.toEqualTypeOf<['github']>() + expectTypeOf(ally.use('github')).toMatchTypeOf() + }) +}) diff --git a/tests/drivers_collection.spec.ts b/tests/drivers_collection.spec.ts new file mode 100644 index 0000000..3ce4b53 --- /dev/null +++ b/tests/drivers_collection.spec.ts @@ -0,0 +1,107 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { test } from '@japa/runner' +import { HttpContextFactory } from '@adonisjs/core/factories/http' + +import { GithubDriver } from '../src/drivers/github.js' +import { GoogleDriver } from '../src/drivers/google.js' +import { TwitterDriver } from '../src/drivers/twitter.js' +import { DiscordDriver } from '../src/drivers/discord.js' +import { SpotifyDriver } from '../src/drivers/spotify.js' +import { FacebookDriver } from '../src/drivers/facebook.js' +import { LinkedInDriver } from '../src/drivers/linked_in.js' +import allyDriversCollection from '../src/drivers_collection.js' +import { + DiscordDriverContract, + FacebookDriverContract, + GithubDriverContract, + GoogleDriverContract, + LinkedInDriverContract, + SpotifyDriverContract, + TwitterDriverContract, +} from '../src/types.js' + +test.group('Drivers Collection', () => { + test('create an instance of a unknown driver', ({ assert, expectTypeOf }) => { + const ctx = new HttpContextFactory().create() + + const discord = allyDriversCollection.create( + 'discord', + { clientId: '', clientSecret: '', callbackUrl: '' }, + ctx + ) + assert.instanceOf(discord, DiscordDriver) + expectTypeOf(discord).toEqualTypeOf() + + const github = allyDriversCollection.create( + 'github', + { clientId: '', clientSecret: '', callbackUrl: '' }, + ctx + ) + assert.instanceOf(github, GithubDriver) + expectTypeOf(github).toEqualTypeOf() + + const google = allyDriversCollection.create( + 'google', + { clientId: '', clientSecret: '', callbackUrl: '' }, + ctx + ) + assert.instanceOf(google, GoogleDriver) + expectTypeOf(google).toEqualTypeOf() + + const facebook = allyDriversCollection.create( + 'facebook', + { clientId: '', clientSecret: '', callbackUrl: '' }, + ctx + ) + assert.instanceOf(facebook, FacebookDriver) + expectTypeOf(facebook).toEqualTypeOf() + + const linkedin = allyDriversCollection.create( + 'linkedin', + { clientId: '', clientSecret: '', callbackUrl: '' }, + ctx + ) + assert.instanceOf(linkedin, LinkedInDriver) + expectTypeOf(linkedin).toEqualTypeOf() + + const spotify = allyDriversCollection.create( + 'spotify', + { clientId: '', clientSecret: '', callbackUrl: '' }, + ctx + ) + assert.instanceOf(spotify, SpotifyDriver) + expectTypeOf(spotify).toEqualTypeOf() + + const twitter = allyDriversCollection.create( + 'twitter', + { clientId: '', clientSecret: '', callbackUrl: '' }, + ctx + ) + assert.instanceOf(twitter, TwitterDriver) + expectTypeOf(twitter).toEqualTypeOf() + }) + + test('extend drivers collection', ({ assert }) => { + class Foo {} + + allyDriversCollection.extend('foo' as any, () => { + return new Foo() + }) + + const ctx = new HttpContextFactory().create() + assert.instanceOf(allyDriversCollection.create('foo' as any, {}, ctx), Foo) + }) + + test('throw error when trying to create an unknown driver', () => { + const ctx = new HttpContextFactory().create() + allyDriversCollection.create('bar' as any, {}, ctx) + }).throws('Unknown ally driver "bar". Make sure the driver is registered') +}) diff --git a/tests/redirect_request.spec.ts b/tests/redirect_request.spec.ts new file mode 100644 index 0000000..6cd494c --- /dev/null +++ b/tests/redirect_request.spec.ts @@ -0,0 +1,55 @@ +/* + * @adonisjs/ally + * + * (c) Ally + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { test } from '@japa/runner' +import { RedirectRequest } from '../src/redirect_request.js' + +test.group('Redirect request', () => { + test('define scopes param', ({ assert }) => { + const redirect = new RedirectRequest('http://foo.com', 'scopes', ',') + redirect.scopes(['username', 'email']) + + assert.deepEqual(redirect.getParams(), { scopes: ['username', 'email'].join(',') }) + }) + + test('merge to existing scopes', ({ assert }) => { + const redirect = new RedirectRequest('http://foo.com', 'scopes', ',') + redirect.scopes(['username', 'email']) + redirect.mergeScopes(['avatar_url']) + + assert.deepEqual(redirect.getParams(), { + scopes: ['username', 'email', 'avatar_url'].join(','), + }) + }) + + test('clear existing scopes', ({ assert }) => { + const redirect = new RedirectRequest('http://foo.com', 'scopes', ',') + redirect.scopes(['username', 'email']) + redirect.clearScopes() + redirect.mergeScopes(['avatar_url']) + + assert.deepEqual(redirect.getParams(), { + scopes: ['avatar_url'].join(','), + }) + }) + + test('use scopes transformer', ({ assert }) => { + const redirect = new RedirectRequest('http://foo.com', 'scopes', ',') + redirect.transformScopes((scopes) => { + return scopes.map((scope) => `foo.com/${scope}`) + }) + + redirect.scopes(['username', 'email']) + redirect.mergeScopes(['avatar_url']) + + assert.deepEqual(redirect.getParams(), { + scopes: ['foo.com/username', 'foo.com/email', 'foo.com/avatar_url'].join(','), + }) + }) +}) From 0d0e864a73ef1dd621832112e0e2058e308c5a79 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 17 Jul 2023 14:21:10 +0530 Subject: [PATCH 06/37] chore: remove file-system dependency to devDependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e38dc67..ceaf4a0 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "@commitlint/config-conventional": "^17.6.6", "@japa/assert": "^2.0.0-1", "@japa/expect-type": "^2.0.0-0", + "@japa/file-system": "^2.0.0-1", "@japa/runner": "^3.0.0-6", "@swc/core": "^1.3.69", "@types/node": "^20.4.2", @@ -72,7 +73,6 @@ "typescript": "^5.1.6" }, "dependencies": { - "@japa/file-system": "^2.0.0-1", "@poppinss/oauth-client": "^5.1.0-3", "@poppinss/utils": "^6.5.0-3" }, From af3cff97be7edeaf1a6ee42ccff062c2f28a33a7 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 17 Jul 2023 14:21:46 +0530 Subject: [PATCH 07/37] chore(release): 5.0.0-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ceaf4a0..2c34bb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/ally", - "version": "4.1.5", + "version": "5.0.0-0", "description": "Social authentication provider for AdonisJS", "type": "module", "main": "build/index.js", From 71ddede3cb0234ebe86ef0990937ae62cd24f00f Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 3 Aug 2023 10:25:15 +0530 Subject: [PATCH 08/37] chore: update dependencies --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2c34bb7..e16baea 100644 --- a/package.json +++ b/package.json @@ -51,19 +51,19 @@ "@adonisjs/eslint-config": "^1.1.8", "@adonisjs/prettier-config": "^1.1.8", "@adonisjs/tsconfig": "^1.1.8", - "@commitlint/cli": "^17.6.6", - "@commitlint/config-conventional": "^17.6.6", + "@commitlint/cli": "^17.6.7", + "@commitlint/config-conventional": "^17.6.7", "@japa/assert": "^2.0.0-1", "@japa/expect-type": "^2.0.0-0", "@japa/file-system": "^2.0.0-1", "@japa/runner": "^3.0.0-6", - "@swc/core": "^1.3.69", - "@types/node": "^20.4.2", - "c8": "^8.0.0", + "@swc/core": "^1.3.74", + "@types/node": "^20.4.6", + "c8": "^8.0.1", "copyfiles": "^2.4.1", "del-cli": "^5.0.0", "dotenv": "^16.3.1", - "eslint": "^8.45.0", + "eslint": "^8.46.0", "github-label-sync": "^2.3.1", "husky": "^8.0.3", "nock": "^13.3.2", From e2cfd59035047edf5c998fc76185078e3df34026 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 3 Aug 2023 10:59:49 +0530 Subject: [PATCH 09/37] refactor: lazily register drivers --- index.ts | 2 +- package.json | 5 +- providers/ally_provider.ts | 8 ++- src/abstract_drivers/oauth1.ts | 2 +- src/abstract_drivers/oauth2.ts | 2 +- src/bindings/http_context.ts | 8 +-- src/define_config.ts | 22 +++++-- src/drivers_collection.ts | 58 ++++++++++++----- src/{exceptions.ts => errors.ts} | 0 tests/ally_manager.spec.ts | 9 ++- tests/ally_provider.spec.ts | 108 +++++++++++++++++++++++++++++++ tests/define_config.spec.ts | 15 ++++- tests/drivers_collection.spec.ts | 15 ++++- 13 files changed, 213 insertions(+), 41 deletions(-) rename src/{exceptions.ts => errors.ts} (100%) create mode 100644 tests/ally_provider.spec.ts diff --git a/index.ts b/index.ts index 7ef217e..2d911bf 100644 --- a/index.ts +++ b/index.ts @@ -11,7 +11,7 @@ import './src/bindings/types.js' export { HttpClient as ApiRequest } from '@poppinss/oauth-client' -export * as errors from './src/exceptions.js' +export * as errors from './src/errors.js' export { AllyManager } from './src/ally_manager.js' export { defineConfig } from './src/define_config.js' export { RedirectRequest } from './src/redirect_request.js' diff --git a/package.json b/package.json index e16baea..71b0fb3 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "author": "adonisjs,virk", "license": "MIT", "devDependencies": { - "@adonisjs/core": "^6.1.5-8", + "@adonisjs/core": "^6.1.5-15", "@adonisjs/eslint-config": "^1.1.8", "@adonisjs/prettier-config": "^1.1.8", "@adonisjs/tsconfig": "^1.1.8", @@ -76,6 +76,9 @@ "@poppinss/oauth-client": "^5.1.0-3", "@poppinss/utils": "^6.5.0-3" }, + "peerDependencies": { + "@adonisjs/core": "^6.1.5-15" + }, "repository": { "type": "git", "url": "git+https://github.com/adonisjs/adonis-ally.git" diff --git a/providers/ally_provider.ts b/providers/ally_provider.ts index e4b6599..f80277d 100644 --- a/providers/ally_provider.ts +++ b/providers/ally_provider.ts @@ -7,7 +7,9 @@ * file that was distributed with this source code. */ -import { ApplicationService } from '@adonisjs/core/types' +import type { ApplicationService } from '@adonisjs/core/types' + +import driversList from '../src/drivers_collection.js' import { extendHttpContext } from '../src/bindings/http_context.js' /** @@ -17,6 +19,8 @@ export default class AllyProvider { constructor(protected app: ApplicationService) {} async boot() { - extendHttpContext(this.app.config.get('ally')) + const config = this.app.config.get('ally') + extendHttpContext(config.services) + await driversList.registerBundledDrivers(config.driversInUse) } } diff --git a/src/abstract_drivers/oauth1.ts b/src/abstract_drivers/oauth1.ts index e3a7f57..6d74e37 100644 --- a/src/abstract_drivers/oauth1.ts +++ b/src/abstract_drivers/oauth1.ts @@ -20,7 +20,7 @@ import { RedirectRequestContract, } from '../types.js' -import * as errors from '../exceptions.js' +import * as errors from '../errors.js' import { RedirectRequest } from '../redirect_request.js' /** diff --git a/src/abstract_drivers/oauth2.ts b/src/abstract_drivers/oauth2.ts index 15f0a39..1dc19f1 100644 --- a/src/abstract_drivers/oauth2.ts +++ b/src/abstract_drivers/oauth2.ts @@ -20,7 +20,7 @@ import { RedirectRequestContract, } from '../types.js' -import * as errors from '../exceptions.js' +import * as errors from '../errors.js' import { RedirectRequest } from '../redirect_request.js' /** diff --git a/src/bindings/http_context.ts b/src/bindings/http_context.ts index 67db21e..47c0e7f 100644 --- a/src/bindings/http_context.ts +++ b/src/bindings/http_context.ts @@ -11,18 +11,16 @@ import { HttpContext } from '@adonisjs/core/http' import './types.js' import { AllyManager } from '../ally_manager.js' -import { AllyManagerDriverFactory, SocialProviders } from '../types.js' +import { AllyManagerDriverFactory } from '../types.js' /** * Extends HttpContext class with the ally getter */ -export function extendHttpContext( - config: SocialProviders extends Record ? SocialProviders : never -) { +export function extendHttpContext(config: Record) { HttpContext.getter( 'ally', function (this: HttpContext) { - return new AllyManager(config, this) + return new AllyManager(config, this) as unknown as HttpContext['ally'] }, true ) diff --git a/src/define_config.ts b/src/define_config.ts index dbe4712..3a45ff4 100644 --- a/src/define_config.ts +++ b/src/define_config.ts @@ -25,17 +25,24 @@ export function defineConfig< >( config: KnownSocialProviders ): { - [K in keyof KnownSocialProviders]: ( - ctx: HttpContext - ) => ReturnType + services: { + [K in keyof KnownSocialProviders]: ( + ctx: HttpContext + ) => ReturnType + } + driversInUse: Set } { /** - * Converting user defined config to an object of providers + * Converting user defined config to an object of services * that can be injected into the AllyManager class */ - const managerHashers = Object.keys(config).reduce( + const driversInUse: Set = new Set() + + const services = Object.keys(config).reduce( (result, provider: keyof KnownSocialProviders) => { const providerConfig = config[provider] + driversInUse.add(providerConfig.driver) + result[provider] = (ctx: HttpContext) => { return allyDriversCollection.create( providerConfig.driver, @@ -52,5 +59,8 @@ export function defineConfig< } ) - return managerHashers + return { + services, + driversInUse, + } } diff --git a/src/drivers_collection.ts b/src/drivers_collection.ts index c69cf54..b8f2663 100644 --- a/src/drivers_collection.ts +++ b/src/drivers_collection.ts @@ -9,29 +9,53 @@ import { RuntimeException } from '@poppinss/utils' import type { HttpContext } from '@adonisjs/core/http' - import type { AllyDriversList } from './types.js' -import { GithubDriver } from './drivers/github.js' -import { GoogleDriver } from './drivers/google.js' -import { SpotifyDriver } from './drivers/spotify.js' -import { TwitterDriver } from './drivers/twitter.js' -import { DiscordDriver } from './drivers/discord.js' -import { FacebookDriver } from './drivers/facebook.js' -import { LinkedInDriver } from './drivers/linked_in.js' +/** + * A global collection of ally drivers. + */ class AllyDriversCollection { + async registerBundledDrivers(drivers: Set) { + if (drivers.has('discord') && !this.list['discord']) { + const { DiscordDriver } = await import('../src/drivers/discord.js') + this.extend('discord', (config, ctx) => new DiscordDriver(ctx, config)) + } + + if (drivers.has('facebook') && !this.list['facebook']) { + const { FacebookDriver } = await import('../src/drivers/facebook.js') + this.extend('facebook', (config, ctx) => new FacebookDriver(ctx, config)) + } + + if (drivers.has('github') && !this.list['github']) { + const { GithubDriver } = await import('../src/drivers/github.js') + this.extend('github', (config, ctx) => new GithubDriver(ctx, config)) + } + + if (drivers.has('google') && !this.list['google']) { + const { GoogleDriver } = await import('../src/drivers/google.js') + this.extend('google', (config, ctx) => new GoogleDriver(ctx, config)) + } + + if (drivers.has('linkedin') && !this.list['linkedin']) { + const { LinkedInDriver } = await import('../src/drivers/linked_in.js') + this.extend('linkedin', (config, ctx) => new LinkedInDriver(ctx, config)) + } + + if (drivers.has('spotify') && !this.list['spotify']) { + const { SpotifyDriver } = await import('../src/drivers/spotify.js') + this.extend('spotify', (config, ctx) => new SpotifyDriver(ctx, config)) + } + + if (drivers.has('twitter') && !this.list['twitter']) { + const { TwitterDriver } = await import('../src/drivers/twitter.js') + this.extend('twitter', (config, ctx) => new TwitterDriver(ctx, config)) + } + } + /** * List of registered drivers */ - list: Partial = { - discord: (config, ctx) => new DiscordDriver(ctx, config), - facebook: (config, ctx) => new FacebookDriver(ctx, config), - github: (config, ctx) => new GithubDriver(ctx, config), - google: (config, ctx) => new GoogleDriver(ctx, config), - linkedin: (config, ctx) => new LinkedInDriver(ctx, config), - spotify: (config, ctx) => new SpotifyDriver(ctx, config), - twitter: (config, ctx) => new TwitterDriver(ctx, config), - } + list: Partial = {} /** * Extend drivers collection and add a custom diff --git a/src/exceptions.ts b/src/errors.ts similarity index 100% rename from src/exceptions.ts rename to src/errors.ts diff --git a/tests/ally_manager.spec.ts b/tests/ally_manager.spec.ts index 2e95920..ac52f0d 100644 --- a/tests/ally_manager.spec.ts +++ b/tests/ally_manager.spec.ts @@ -12,8 +12,15 @@ import { HttpContextFactory } from '@adonisjs/core/factories/http' import { AllyManager } from '../src/ally_manager.js' import { GithubDriver } from '../src/drivers/github.js' +import allyDriversCollection from '../src/drivers_collection.js' + +test.group('Ally manager', (group) => { + group.each.setup(() => { + return () => { + allyDriversCollection.list = {} + } + }) -test.group('Ally manager', () => { test('create an instance of a driver', ({ assert, expectTypeOf }) => { const ctx = new HttpContextFactory().create() diff --git a/tests/ally_provider.spec.ts b/tests/ally_provider.spec.ts new file mode 100644 index 0000000..4c11bf2 --- /dev/null +++ b/tests/ally_provider.spec.ts @@ -0,0 +1,108 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { test } from '@japa/runner' +import { IgnitorFactory } from '@adonisjs/core/factories' +import { HttpContextFactory } from '@adonisjs/core/factories/http' + +import { AllyManager } from '../src/ally_manager.js' +import { defineConfig } from '../src/define_config.js' +import allyDriversCollection from '../src/drivers_collection.js' + +const BASE_URL = new URL('./tmp/', import.meta.url) +const IMPORTER = (filePath: string) => { + if (filePath.startsWith('./') || filePath.startsWith('../')) { + return import(new URL(filePath, BASE_URL).href) + } + return import(filePath) +} + +test.group('Ally provider', (group) => { + group.each.setup(() => { + return () => { + allyDriversCollection.list = {} + } + }) + + test('register drivers in use', async ({ assert }) => { + const ignitor = new IgnitorFactory() + .merge({ + rcFileContents: { + providers: ['../../providers/ally_provider.js'], + }, + }) + .withCoreConfig() + .withCoreProviders() + .merge({ + config: { + ally: defineConfig({ + github: { + driver: 'github', + clientId: '', + clientSecret: '', + callbackUrl: '', + }, + }), + }, + }) + .create(BASE_URL, { + importer: IMPORTER, + }) + + const app = ignitor.createApp('web') + await app.init() + await app.boot() + + assert.property(allyDriversCollection.list, 'github') + assert.notAllProperties(allyDriversCollection.list, [ + 'facebook', + 'linkedin', + 'google', + 'spotify', + 'twitter', + 'discord', + ]) + }) + + test('add ally to HttpContext', async ({ assert }) => { + const ignitor = new IgnitorFactory() + .merge({ + rcFileContents: { + providers: ['../../providers/ally_provider.js'], + }, + }) + .withCoreConfig() + .withCoreProviders() + .merge({ + config: { + ally: defineConfig({ + github: { + driver: 'github', + clientId: '', + clientSecret: '', + callbackUrl: '', + }, + }), + }, + }) + .create(BASE_URL, { + importer: IMPORTER, + }) + + const app = ignitor.createApp('web') + await app.init() + await app.boot() + + const ctx = new HttpContextFactory().create() + assert.instanceOf(ctx.ally, AllyManager) + + // Should be singleton + assert.strictEqual(ctx.ally, ctx.ally) + }) +}) diff --git a/tests/define_config.spec.ts b/tests/define_config.spec.ts index 4e96f4a..791bd54 100644 --- a/tests/define_config.spec.ts +++ b/tests/define_config.spec.ts @@ -13,9 +13,16 @@ import { HttpContextFactory } from '@adonisjs/core/factories/http' import { AllyManager, defineConfig } from '../index.js' import { GithubDriver } from '../src/drivers/github.js' import type { GithubDriverContract } from '../src/types.js' +import allyDriversCollection from '../src/drivers_collection.js' -test.group('Define config', () => { - test('define manager config from user defined config', ({ assert, expectTypeOf }) => { +test.group('Define config', (group) => { + group.each.setup(() => { + return () => { + allyDriversCollection.list = {} + } + }) + + test('define manager config from user defined config', async ({ assert, expectTypeOf }) => { const managerConfig = defineConfig({ github: { driver: 'github', @@ -27,7 +34,9 @@ test.group('Define config', () => { }) const ctx = new HttpContextFactory().create() - const ally = new AllyManager(managerConfig, ctx) + const ally = new AllyManager(managerConfig.services, ctx) + + await allyDriversCollection.registerBundledDrivers(managerConfig.driversInUse) assert.instanceOf(ally.use('github'), GithubDriver) assert.strictEqual(ally.use('github'), ally.use('github')) diff --git a/tests/drivers_collection.spec.ts b/tests/drivers_collection.spec.ts index 3ce4b53..f6434ca 100644 --- a/tests/drivers_collection.spec.ts +++ b/tests/drivers_collection.spec.ts @@ -18,7 +18,7 @@ import { SpotifyDriver } from '../src/drivers/spotify.js' import { FacebookDriver } from '../src/drivers/facebook.js' import { LinkedInDriver } from '../src/drivers/linked_in.js' import allyDriversCollection from '../src/drivers_collection.js' -import { +import type { DiscordDriverContract, FacebookDriverContract, GithubDriverContract, @@ -28,9 +28,18 @@ import { TwitterDriverContract, } from '../src/types.js' -test.group('Drivers Collection', () => { - test('create an instance of a unknown driver', ({ assert, expectTypeOf }) => { +test.group('Drivers Collection', (group) => { + group.each.setup(() => { + return () => { + allyDriversCollection.list = {} + } + }) + + test('create an instance of a known driver', async ({ assert, expectTypeOf }) => { const ctx = new HttpContextFactory().create() + await allyDriversCollection.registerBundledDrivers( + new Set(['github', 'google', 'discord', 'facebook', 'linkedin', 'spotify', 'twitter']) + ) const discord = allyDriversCollection.create( 'discord', From 504e30842c63e399f340f289d0b9f4d4eea2bcb0 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 3 Aug 2023 11:03:31 +0530 Subject: [PATCH 10/37] refactor: add debug logs --- src/debug.ts | 12 ++++++++++++ src/drivers_collection.ts | 5 +++++ 2 files changed, 17 insertions(+) create mode 100644 src/debug.ts diff --git a/src/debug.ts b/src/debug.ts new file mode 100644 index 0000000..d6c47bf --- /dev/null +++ b/src/debug.ts @@ -0,0 +1,12 @@ +/* + * @adonisjs/ally + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { debuglog } from 'node:util' + +export default debuglog('adonisjs:ally') diff --git a/src/drivers_collection.ts b/src/drivers_collection.ts index b8f2663..c7aef0f 100644 --- a/src/drivers_collection.ts +++ b/src/drivers_collection.ts @@ -10,12 +10,15 @@ import { RuntimeException } from '@poppinss/utils' import type { HttpContext } from '@adonisjs/core/http' import type { AllyDriversList } from './types.js' +import debug from './debug.js' /** * A global collection of ally drivers. */ class AllyDriversCollection { async registerBundledDrivers(drivers: Set) { + debug('drivers in use %O', drivers) + if (drivers.has('discord') && !this.list['discord']) { const { DiscordDriver } = await import('../src/drivers/discord.js') this.extend('discord', (config, ctx) => new DiscordDriver(ctx, config)) @@ -65,6 +68,7 @@ class AllyDriversCollection { driverName: Name, factoryCallback: AllyDriversList[Name] ): this { + debug('registering %s driver', driverName) this.list[driverName] = factoryCallback return this } @@ -84,6 +88,7 @@ class AllyDriversCollection { ) } + debug('creating instance of %s driver', name) return driverFactory(config as any, ctx) as ReturnType } } From 2e10bdce52caefe60d18e73b01cab27ee1f9b149 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 3 Aug 2023 11:06:18 +0530 Subject: [PATCH 11/37] refactor: fix typing issues --- src/types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 9849f88..180bbbc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -627,4 +627,5 @@ export type AllyManagerDriverFactory = (ctx: HttpContext) => AllyDriverContract< * from the config file */ export interface SocialProviders {} -export type InferSocialProviders> = T +export type InferSocialProviders }> = + T['services'] From 22d6da6facb1ef64c63f408c1aecdd2bf8058410 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 3 Aug 2023 11:42:28 +0530 Subject: [PATCH 12/37] chore(release): 5.0.0-1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 71b0fb3..9dd496f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/ally", - "version": "5.0.0-0", + "version": "5.0.0-1", "description": "Social authentication provider for AdonisJS", "type": "module", "main": "build/index.js", From 3d55c22f138ba34c0fee057fdf43164064188869 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 3 Aug 2023 13:24:02 +0530 Subject: [PATCH 13/37] fix: generate config files with default config --- configure.ts | 30 ++++++++++++++++++++++++++++-- stubs/config.stub | 19 +++++++++++++++++-- stubs/types.stub | 12 ------------ tests/configure.spec.ts | 29 +++++++++++++++++++++++++++-- 4 files changed, 72 insertions(+), 18 deletions(-) delete mode 100644 stubs/types.stub diff --git a/configure.ts b/configure.ts index 567abf9..d52daf2 100644 --- a/configure.ts +++ b/configure.ts @@ -13,9 +13,35 @@ import type Configure from '@adonisjs/core/commands/configure' * Configures the package */ export async function configure(command: Configure) { - await command.publishStub('config.stub') - await command.publishStub('types.stub') + const providers = await command.prompt.multiple( + 'Select the social auth providers you plan to use', + ['discord', 'facebook', 'github', 'google', 'linkedin', 'spotify', 'twitter'] + ) + + /** + * Publish config file + */ + await command.publishStub('config.stub', { + providers: providers.map((provider) => { + return { provider, envPrefix: provider.toUpperCase() } + }), + }) + + /** + * Publish provider + */ await command.updateRcFile((rcFile) => { rcFile.addProvider('@adonisjs/ally/ally_provider') }) + + /** + * Define env variables for the selected providers + */ + await command.defineEnvVariables( + providers.reduce>((result, provider) => { + result[`${provider.toUpperCase()}_CLIENT_ID`] = '' + result[`${provider.toUpperCase()}_CLIENT_SECRET`] = '' + return result + }, {}) + ) } diff --git a/stubs/config.stub b/stubs/config.stub index 70cda38..4d0a32d 100644 --- a/stubs/config.stub +++ b/stubs/config.stub @@ -1,7 +1,22 @@ --- to: {{ app.configPath('ally.ts') }} --- -import { defineConfig } from '@adonisjs/ally' import env from '#start/env' +import { defineConfig } from '@adonisjs/ally' + +const allyConfig = defineConfig({ +{{#each providers as provider}} + {{provider.provider}}: { + driver: '{{provider.provider}}', + clientId: env.get('{{provider.envPrefix}}_CLIENT_ID'), + clientSecret: env.get('{{provider.envPrefix}}_CLIENT_SECRET'), + callbackUrl: '', + }, +{{/each}} +}) + +export default allyConfig -export default defineConfig({}) +declare module '@adonisjs/ally/types' { + interface SocialProviders extends InferSocialProviders {} +} diff --git a/stubs/types.stub b/stubs/types.stub deleted file mode 100644 index 258f039..0000000 --- a/stubs/types.stub +++ /dev/null @@ -1,12 +0,0 @@ ---- -to: {{ app.contractsPath('ally.ts') }} ---- -import allyConfig from '#config/ally' - -declare module '@adonisjs/ally/types' { - /** - * Here we get a list of social providers you have configured inside - * the config/ally.ts file. - */ - interface SocialProviders extends InferSocialProviders {} -} diff --git a/tests/configure.spec.ts b/tests/configure.spec.ts index a9c0b9f..c7b2c39 100644 --- a/tests/configure.spec.ts +++ b/tests/configure.spec.ts @@ -20,7 +20,7 @@ test.group('Configure', (group) => { context.fs.basePath = fileURLToPath(BASE_URL) }) - test('create config file and register provider', async ({ assert }) => { + test('create config file and register provider', async ({ fs, assert }) => { const ignitor = new IgnitorFactory() .withCoreProviders() .withCoreConfig() @@ -37,15 +37,40 @@ test.group('Configure', (group) => { const app = ignitor.createApp('web') await app.init() await app.boot() + await fs.create('.env', '') const ace = await app.container.make('ace') + ace.prompt.trap('Select the social auth providers you plan to use').chooseOptions([2, 4]) + const command = await ace.create(Configure, ['../../index.js']) await command.exec() await assert.fileExists('config/ally.ts') - await assert.fileExists('contracts/ally.ts') await assert.fileExists('.adonisrc.json') await assert.fileContains('.adonisrc.json', '@adonisjs/ally/ally_provider') await assert.fileContains('config/ally.ts', 'defineConfig') + await assert.fileContains('config/ally.ts', `declare module '@adonisjs/ally/types' {`) + await assert.fileContains( + 'config/ally.ts', + `github: { + driver: 'github', + clientId: env.get('GITHUB_CLIENT_ID'), + clientSecret: env.get('GITHUB_CLIENT_SECRET'), + callbackUrl: '', + },` + ) + await assert.fileContains( + 'config/ally.ts', + `linkedin: { + driver: 'linkedin', + clientId: env.get('LINKEDIN_CLIENT_ID'), + clientSecret: env.get('LINKEDIN_CLIENT_SECRET'), + callbackUrl: '', + },` + ) + await assert.fileContains('.env', 'GITHUB_CLIENT_ID') + await assert.fileContains('.env', 'GITHUB_CLIENT_SECRET') + await assert.fileContains('.env', 'LINKEDIN_CLIENT_ID') + await assert.fileContains('.env', 'LINKEDIN_CLIENT_SECRET') }) }) From 6388baee0e5e6c99d710d341b0870d1f63da1210 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 3 Aug 2023 13:27:57 +0530 Subject: [PATCH 14/37] chore(release): 5.0.0-2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9dd496f..3a04c45 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/ally", - "version": "5.0.0-1", + "version": "5.0.0-2", "description": "Social authentication provider for AdonisJS", "type": "module", "main": "build/index.js", From 6feab0645c6a6e68bf6b3bb613b9ed4975ef8ac6 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 21 Aug 2023 17:25:49 +0530 Subject: [PATCH 15/37] chore: update dependencies --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 3a04c45..edc618b 100644 --- a/package.json +++ b/package.json @@ -47,28 +47,28 @@ "author": "adonisjs,virk", "license": "MIT", "devDependencies": { - "@adonisjs/core": "^6.1.5-15", + "@adonisjs/core": "^6.1.5-18", "@adonisjs/eslint-config": "^1.1.8", "@adonisjs/prettier-config": "^1.1.8", "@adonisjs/tsconfig": "^1.1.8", - "@commitlint/cli": "^17.6.7", - "@commitlint/config-conventional": "^17.6.7", + "@commitlint/cli": "^17.7.1", + "@commitlint/config-conventional": "^17.7.0", "@japa/assert": "^2.0.0-1", "@japa/expect-type": "^2.0.0-0", "@japa/file-system": "^2.0.0-1", "@japa/runner": "^3.0.0-6", - "@swc/core": "^1.3.74", - "@types/node": "^20.4.6", + "@swc/core": "^1.3.78", + "@types/node": "^20.5.1", "c8": "^8.0.1", "copyfiles": "^2.4.1", "del-cli": "^5.0.0", "dotenv": "^16.3.1", - "eslint": "^8.46.0", + "eslint": "^8.47.0", "github-label-sync": "^2.3.1", "husky": "^8.0.3", - "nock": "^13.3.2", + "nock": "^13.3.3", "np": "^8.0.4", - "prettier": "^3.0.0", + "prettier": "^3.0.2", "ts-node": "^10.9.1", "typescript": "^5.1.6" }, @@ -77,7 +77,7 @@ "@poppinss/utils": "^6.5.0-3" }, "peerDependencies": { - "@adonisjs/core": "^6.1.5-15" + "@adonisjs/core": "^6.1.5-18" }, "repository": { "type": "git", From ed42e3fdc4981ec00651abec36ca1ba75e37279a Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 21 Aug 2023 17:28:32 +0530 Subject: [PATCH 16/37] feat: use latest codemods --- configure.ts | 12 ++++++++++++ package.json | 1 + tests/configure.spec.ts | 13 +++++++++++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/configure.ts b/configure.ts index d52daf2..d07971d 100644 --- a/configure.ts +++ b/configure.ts @@ -44,4 +44,16 @@ export async function configure(command: Configure) { return result }, {}) ) + + /** + * Define env variables validation for the selected providers + */ + await command.defineEnvValidations({ + variables: providers.reduce>((result, provider) => { + result[`${provider.toUpperCase()}_CLIENT_ID`] = 'Env.schema.string()' + result[`${provider.toUpperCase()}_CLIENT_SECRET`] = 'Env.schema.string()' + return result + }, {}), + leadingComment: 'Variables for configuring ally package', + }) } diff --git a/package.json b/package.json index edc618b..95115a7 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "author": "adonisjs,virk", "license": "MIT", "devDependencies": { + "@adonisjs/assembler": "^6.1.3-18", "@adonisjs/core": "^6.1.5-18", "@adonisjs/eslint-config": "^1.1.8", "@adonisjs/prettier-config": "^1.1.8", diff --git a/tests/configure.spec.ts b/tests/configure.spec.ts index c7b2c39..84d3418 100644 --- a/tests/configure.spec.ts +++ b/tests/configure.spec.ts @@ -37,7 +37,11 @@ test.group('Configure', (group) => { const app = ignitor.createApp('web') await app.init() await app.boot() + await fs.create('.env', '') + await fs.createJson('tsconfig.json', {}) + await fs.create('start/env.ts', `export default Env.create(new URL('./'), {})`) + await fs.create('adonisrc.ts', `export default defineConfig({})`) const ace = await app.container.make('ace') ace.prompt.trap('Select the social auth providers you plan to use').chooseOptions([2, 4]) @@ -46,8 +50,8 @@ test.group('Configure', (group) => { await command.exec() await assert.fileExists('config/ally.ts') - await assert.fileExists('.adonisrc.json') - await assert.fileContains('.adonisrc.json', '@adonisjs/ally/ally_provider') + await assert.fileExists('adonisrc.ts') + await assert.fileContains('adonisrc.ts', '@adonisjs/ally/ally_provider') await assert.fileContains('config/ally.ts', 'defineConfig') await assert.fileContains('config/ally.ts', `declare module '@adonisjs/ally/types' {`) await assert.fileContains( @@ -72,5 +76,10 @@ test.group('Configure', (group) => { await assert.fileContains('.env', 'GITHUB_CLIENT_SECRET') await assert.fileContains('.env', 'LINKEDIN_CLIENT_ID') await assert.fileContains('.env', 'LINKEDIN_CLIENT_SECRET') + + await assert.fileContains('start/env.ts', 'GITHUB_CLIENT_ID: Env.schema.string()') + await assert.fileContains('start/env.ts', 'GITHUB_CLIENT_SECRET: Env.schema.string()') + await assert.fileContains('start/env.ts', 'LINKEDIN_CLIENT_ID: Env.schema.string()') + await assert.fileContains('start/env.ts', 'LINKEDIN_CLIENT_SECRET: Env.schema.string()') }) }) From f0f141d113625982a165be94236408cafcb58801 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 21 Aug 2023 17:32:18 +0530 Subject: [PATCH 17/37] ci: increase test timeout for ci --- tests/configure.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/configure.spec.ts b/tests/configure.spec.ts index 84d3418..88dd5ed 100644 --- a/tests/configure.spec.ts +++ b/tests/configure.spec.ts @@ -81,5 +81,5 @@ test.group('Configure', (group) => { await assert.fileContains('start/env.ts', 'GITHUB_CLIENT_SECRET: Env.schema.string()') await assert.fileContains('start/env.ts', 'LINKEDIN_CLIENT_ID: Env.schema.string()') await assert.fileContains('start/env.ts', 'LINKEDIN_CLIENT_SECRET: Env.schema.string()') - }) + }).timeout(6000) }) From b73bebf48dc069a70d7b68fbea4e88379abf00a2 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 21 Aug 2023 17:36:07 +0530 Subject: [PATCH 18/37] chore(release): 5.0.0-3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 95115a7..524e143 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/ally", - "version": "5.0.0-2", + "version": "5.0.0-3", "description": "Social authentication provider for AdonisJS", "type": "module", "main": "build/index.js", From bffc36a57788fbb1d1440e8f1dd20863878a8fd4 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 22 Aug 2023 11:46:05 +0530 Subject: [PATCH 19/37] chore: update dependencies --- configure.ts | 8 +++++--- package.json | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/configure.ts b/configure.ts index d07971d..6485700 100644 --- a/configure.ts +++ b/configure.ts @@ -27,17 +27,19 @@ export async function configure(command: Configure) { }), }) + const codemods = await command.createCodemods() + /** * Publish provider */ - await command.updateRcFile((rcFile) => { + await codemods.updateRcFile((rcFile) => { rcFile.addProvider('@adonisjs/ally/ally_provider') }) /** * Define env variables for the selected providers */ - await command.defineEnvVariables( + await codemods.defineEnvVariables( providers.reduce>((result, provider) => { result[`${provider.toUpperCase()}_CLIENT_ID`] = '' result[`${provider.toUpperCase()}_CLIENT_SECRET`] = '' @@ -48,7 +50,7 @@ export async function configure(command: Configure) { /** * Define env variables validation for the selected providers */ - await command.defineEnvValidations({ + await codemods.defineEnvValidations({ variables: providers.reduce>((result, provider) => { result[`${provider.toUpperCase()}_CLIENT_ID`] = 'Env.schema.string()' result[`${provider.toUpperCase()}_CLIENT_SECRET`] = 'Env.schema.string()' diff --git a/package.json b/package.json index 524e143..21ee534 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "license": "MIT", "devDependencies": { "@adonisjs/assembler": "^6.1.3-18", - "@adonisjs/core": "^6.1.5-18", + "@adonisjs/core": "^6.1.5-19", "@adonisjs/eslint-config": "^1.1.8", "@adonisjs/prettier-config": "^1.1.8", "@adonisjs/tsconfig": "^1.1.8", @@ -78,7 +78,7 @@ "@poppinss/utils": "^6.5.0-3" }, "peerDependencies": { - "@adonisjs/core": "^6.1.5-18" + "@adonisjs/core": "^6.1.5-19" }, "repository": { "type": "git", From 23d7acbf01635fb3ac518e30e5e6d7b04c595e9d Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 22 Aug 2023 11:48:05 +0530 Subject: [PATCH 20/37] chore(release): 5.0.0-4 --- package.json | 22 +++++++++++----------- src/abstract_drivers/oauth1.ts | 2 +- src/abstract_drivers/oauth2.ts | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 21ee534..f8aea87 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/ally", - "version": "5.0.0-3", + "version": "5.0.0-4", "description": "Social authentication provider for AdonisJS", "type": "module", "main": "build/index.js", @@ -47,8 +47,8 @@ "author": "adonisjs,virk", "license": "MIT", "devDependencies": { - "@adonisjs/assembler": "^6.1.3-18", - "@adonisjs/core": "^6.1.5-19", + "@adonisjs/assembler": "^6.1.3-22", + "@adonisjs/core": "^6.1.5-26", "@adonisjs/eslint-config": "^1.1.8", "@adonisjs/prettier-config": "^1.1.8", "@adonisjs/tsconfig": "^1.1.8", @@ -58,24 +58,24 @@ "@japa/expect-type": "^2.0.0-0", "@japa/file-system": "^2.0.0-1", "@japa/runner": "^3.0.0-6", - "@swc/core": "^1.3.78", - "@types/node": "^20.5.1", + "@swc/core": "1.3.82", + "@types/node": "^20.7.0", "c8": "^8.0.1", "copyfiles": "^2.4.1", - "del-cli": "^5.0.0", + "del-cli": "^5.1.0", "dotenv": "^16.3.1", - "eslint": "^8.47.0", + "eslint": "^8.50.0", "github-label-sync": "^2.3.1", "husky": "^8.0.3", "nock": "^13.3.3", "np": "^8.0.4", - "prettier": "^3.0.2", + "prettier": "^3.0.3", "ts-node": "^10.9.1", - "typescript": "^5.1.6" + "typescript": "^5.2.2" }, "dependencies": { - "@poppinss/oauth-client": "^5.1.0-3", - "@poppinss/utils": "^6.5.0-3" + "@poppinss/oauth-client": "^5.1.0-4", + "@poppinss/utils": "^6.5.0-7" }, "peerDependencies": { "@adonisjs/core": "^6.1.5-19" diff --git a/src/abstract_drivers/oauth1.ts b/src/abstract_drivers/oauth1.ts index 6d74e37..a6ded05 100644 --- a/src/abstract_drivers/oauth1.ts +++ b/src/abstract_drivers/oauth1.ts @@ -8,7 +8,7 @@ */ import { Exception } from '@poppinss/utils' -import { Oauth1Client } from '@poppinss/oauth-client' +import { Oauth1Client } from '@poppinss/oauth-client/oauth1' import type { HttpContext } from '@adonisjs/core/http' import { diff --git a/src/abstract_drivers/oauth2.ts b/src/abstract_drivers/oauth2.ts index 1dc19f1..7cf89e8 100644 --- a/src/abstract_drivers/oauth2.ts +++ b/src/abstract_drivers/oauth2.ts @@ -8,7 +8,7 @@ */ import { Exception } from '@poppinss/utils' -import { Oauth2Client } from '@poppinss/oauth-client' +import { Oauth2Client } from '@poppinss/oauth-client/oauth2' import type { HttpContext } from '@adonisjs/core/http' import { From 11e584d3023a4021a78495956967a9ecf3d12483 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 27 Sep 2023 14:40:34 +0530 Subject: [PATCH 21/37] chore(release): 5.0.0-5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f8aea87..220e2cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/ally", - "version": "5.0.0-4", + "version": "5.0.0-5", "description": "Social authentication provider for AdonisJS", "type": "module", "main": "build/index.js", From c9b5a5cc37480f054dedcc609e6bc5f96791d00a Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 19 Oct 2023 15:29:14 +0530 Subject: [PATCH 22/37] chore: update dependencies --- package.json | 28 ++++++++++++++-------------- stubs/config.stub | 6 +++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 220e2cb..9dd20da 100644 --- a/package.json +++ b/package.json @@ -47,38 +47,38 @@ "author": "adonisjs,virk", "license": "MIT", "devDependencies": { - "@adonisjs/assembler": "^6.1.3-22", - "@adonisjs/core": "^6.1.5-26", + "@adonisjs/assembler": "^6.1.3-25", + "@adonisjs/core": "^6.1.5-28", "@adonisjs/eslint-config": "^1.1.8", "@adonisjs/prettier-config": "^1.1.8", "@adonisjs/tsconfig": "^1.1.8", - "@commitlint/cli": "^17.7.1", - "@commitlint/config-conventional": "^17.7.0", - "@japa/assert": "^2.0.0-1", - "@japa/expect-type": "^2.0.0-0", - "@japa/file-system": "^2.0.0-1", - "@japa/runner": "^3.0.0-6", + "@commitlint/cli": "^17.8.0", + "@commitlint/config-conventional": "^17.8.0", + "@japa/assert": "^2.0.0", + "@japa/expect-type": "^2.0.0", + "@japa/file-system": "^2.0.0", + "@japa/runner": "^3.0.4", "@swc/core": "1.3.82", - "@types/node": "^20.7.0", + "@types/node": "^20.8.7", "c8": "^8.0.1", "copyfiles": "^2.4.1", "del-cli": "^5.1.0", "dotenv": "^16.3.1", - "eslint": "^8.50.0", + "eslint": "^8.51.0", "github-label-sync": "^2.3.1", "husky": "^8.0.3", - "nock": "^13.3.3", + "nock": "^13.3.4", "np": "^8.0.4", "prettier": "^3.0.3", "ts-node": "^10.9.1", "typescript": "^5.2.2" }, "dependencies": { - "@poppinss/oauth-client": "^5.1.0-4", - "@poppinss/utils": "^6.5.0-7" + "@poppinss/oauth-client": "^5.1.0", + "@poppinss/utils": "^6.5.0" }, "peerDependencies": { - "@adonisjs/core": "^6.1.5-19" + "@adonisjs/core": "^6.1.5-28" }, "repository": { "type": "git", diff --git a/stubs/config.stub b/stubs/config.stub index 4d0a32d..b49cb87 100644 --- a/stubs/config.stub +++ b/stubs/config.stub @@ -1,6 +1,6 @@ ---- -to: {{ app.configPath('ally.ts') }} ---- +{{{ + exports({ to: app.configPath('ally.ts') }) +}}} import env from '#start/env' import { defineConfig } from '@adonisjs/ally' From c1e0bc17b5625e8972b07307308d86ecfde40703 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 19 Oct 2023 19:33:01 +0530 Subject: [PATCH 23/37] refactor: move to config providers and remove drivers collection --- index.ts | 8 +- providers/ally_provider.ts | 38 ++++++- src/abstract_drivers/oauth1.ts | 2 +- src/abstract_drivers/oauth2.ts | 2 +- src/bindings/http_context.ts | 27 ----- src/bindings/types.ts | 25 ----- src/defaults/config.ts | 69 ------------- src/define_config.ts | 145 ++++++++++++++++++-------- src/drivers/discord.ts | 12 +-- src/drivers/facebook.ts | 11 +- src/drivers/github.ts | 11 +- src/drivers/google.ts | 11 +- src/drivers/linked_in.ts | 11 +- src/drivers/spotify.ts | 11 +- src/drivers/twitter.ts | 6 +- src/drivers_collection.ts | 97 ------------------ src/types.ts | 70 ++++--------- stubs/config.stub | 7 +- tests/ally_manager.spec.ts | 9 +- tests/ally_provider.spec.ts | 63 ++---------- tests/configure.spec.ts | 12 +-- tests/define_config.spec.ts | 171 +++++++++++++++++++++++++++---- tests/drivers_collection.spec.ts | 116 --------------------- 23 files changed, 354 insertions(+), 580 deletions(-) delete mode 100644 src/bindings/http_context.ts delete mode 100644 src/bindings/types.ts delete mode 100644 src/defaults/config.ts delete mode 100644 src/drivers_collection.ts delete mode 100644 tests/drivers_collection.spec.ts diff --git a/index.ts b/index.ts index 2d911bf..1f58a55 100644 --- a/index.ts +++ b/index.ts @@ -7,16 +7,14 @@ * file that was distributed with this source code. */ -import './src/bindings/types.js' - export { HttpClient as ApiRequest } from '@poppinss/oauth-client' export * as errors from './src/errors.js' +export { configure } from './configure.js' +export { stubsRoot } from './stubs/main.js' export { AllyManager } from './src/ally_manager.js' export { defineConfig } from './src/define_config.js' + export { RedirectRequest } from './src/redirect_request.js' export { Oauth1Driver } from './src/abstract_drivers/oauth1.js' export { Oauth2Driver } from './src/abstract_drivers/oauth2.js' -export { default as driversList } from './src/drivers_collection.js' -export { stubsRoot } from './stubs/main.js' -export { configure } from './configure.js' diff --git a/providers/ally_provider.ts b/providers/ally_provider.ts index f80277d..85df942 100644 --- a/providers/ally_provider.ts +++ b/providers/ally_provider.ts @@ -7,10 +7,19 @@ * file that was distributed with this source code. */ +import { configProvider } from '@adonisjs/core' +import { HttpContext } from '@adonisjs/core/http' +import { RuntimeException } from '@poppinss/utils' import type { ApplicationService } from '@adonisjs/core/types' -import driversList from '../src/drivers_collection.js' -import { extendHttpContext } from '../src/bindings/http_context.js' +import type { AllyService } from '../src/types.js' +import { AllyManager } from '../src/ally_manager.js' + +declare module '@adonisjs/core/http' { + export interface HttpContext { + ally: AllyService + } +} /** * AllyProvider extends the HTTP context with the "ally" property @@ -19,8 +28,27 @@ export default class AllyProvider { constructor(protected app: ApplicationService) {} async boot() { - const config = this.app.config.get('ally') - extendHttpContext(config.services) - await driversList.registerBundledDrivers(config.driversInUse) + const allyConfigProvider = this.app.config.get('ally') + + /** + * Resolve config from the provider + */ + const config = await configProvider.resolve(this.app, allyConfigProvider) + if (!config) { + throw new RuntimeException( + 'Invalid "config/ally.ts" file. Make sure you are using the "defineConfig" method' + ) + } + + /** + * Setup HTTPContext getter + */ + HttpContext.getter( + 'ally', + function (this: HttpContext) { + return new AllyManager(config, this) as unknown as AllyService + }, + true + ) } } diff --git a/src/abstract_drivers/oauth1.ts b/src/abstract_drivers/oauth1.ts index a6ded05..148a199 100644 --- a/src/abstract_drivers/oauth1.ts +++ b/src/abstract_drivers/oauth1.ts @@ -8,8 +8,8 @@ */ import { Exception } from '@poppinss/utils' -import { Oauth1Client } from '@poppinss/oauth-client/oauth1' import type { HttpContext } from '@adonisjs/core/http' +import { Oauth1Client } from '@poppinss/oauth-client/oauth1' import { AllyUserContract, diff --git a/src/abstract_drivers/oauth2.ts b/src/abstract_drivers/oauth2.ts index 7cf89e8..90ee02f 100644 --- a/src/abstract_drivers/oauth2.ts +++ b/src/abstract_drivers/oauth2.ts @@ -8,8 +8,8 @@ */ import { Exception } from '@poppinss/utils' -import { Oauth2Client } from '@poppinss/oauth-client/oauth2' import type { HttpContext } from '@adonisjs/core/http' +import { Oauth2Client } from '@poppinss/oauth-client/oauth2' import { AllyUserContract, diff --git a/src/bindings/http_context.ts b/src/bindings/http_context.ts deleted file mode 100644 index 47c0e7f..0000000 --- a/src/bindings/http_context.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) AdonisJS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { HttpContext } from '@adonisjs/core/http' - -import './types.js' -import { AllyManager } from '../ally_manager.js' -import { AllyManagerDriverFactory } from '../types.js' - -/** - * Extends HttpContext class with the ally getter - */ -export function extendHttpContext(config: Record) { - HttpContext.getter( - 'ally', - function (this: HttpContext) { - return new AllyManager(config, this) as unknown as HttpContext['ally'] - }, - true - ) -} diff --git a/src/bindings/types.ts b/src/bindings/types.ts deleted file mode 100644 index 23bfa85..0000000 --- a/src/bindings/types.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) AdonisJS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import type { AllyManager } from '../ally_manager.js' -import type { AllyManagerDriverFactory, SocialProviders } from '../types.js' - -/** - * In order for types to get picked up, this module must get - * imported by TypeScript. Therefore, we export this module - * from the package entrypoint - */ - -declare module '@adonisjs/core/http' { - interface HttpContext { - ally: AllyManager< - SocialProviders extends Record ? SocialProviders : never - > - } -} diff --git a/src/defaults/config.ts b/src/defaults/config.ts deleted file mode 100644 index 76af456..0000000 --- a/src/defaults/config.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) AdonisJS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -export const twitter = { - REQUEST_TOKEN_URL: 'https://api.twitter.com/oauth/request_token', - AUTHORIZE_URL: 'https://api.twitter.com/oauth/authenticate', - ACCESS_TOKEN_URL: 'https://api.twitter.com/oauth/access_token', -} - -export const github = { - AUTHORIZE_URL: 'https://github.com/login/oauth/authorize', - ACCESS_TOKEN_URL: 'https://github.com/login/oauth/access_token', - USER_INFO_URL: 'https://api.github.com/user', - USER_EMAIL_URL: 'https://api.github.com/user/emails', -} - -export const google = { - AUTHORIZE_URL: 'https://accounts.google.com/o/oauth2/v2/auth', - ACCESS_TOKEN_URL: 'https://oauth2.googleapis.com/token', - USER_INFO_URL: 'https://www.googleapis.com/oauth2/v3/userinfo', -} - -export const gitlab = { - AUTHORIZE_URL: 'https://gitlab.com/oauth/authorize', - ACCESS_TOKEN_URL: 'https://gitlab.com/oauth/token', -} - -export const linkedin = { - AUTHORIZE_URL: 'https://www.linkedin.com/oauth/v2/authorization', - ACCESS_TOKEN_URL: 'https://www.linkedin.com/oauth/v2/accessToken', -} - -export const patreon = { - AUTHORIZE_URL: 'https://www.patreon.com/oauth2/authorize', - ACCESS_TOKEN_URL: 'https://www.patreon.com/api/oauth2/token', -} - -export const discord = { - AUTHORIZE_URL: 'https://discord.com/api/oauth2/authorize', - ACCESS_TOKEN_URL: 'https://discord.com/api/oauth2/token', - USER_INFO_URL: 'https://discord.com/api/users/@me', -} - -export const microsoft = { - AUTHORIZE_URL: 'https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize', - ACCESS_TOKEN_URL: 'https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token', -} - -export const bitbucket = { - AUTHORIZE_URL: 'https://bitbucket.org/site/oauth2/authorize', - ACCESS_TOKEN_URL: 'https://bitbucket.org/site/oauth2/access_token', -} - -export const facebook = { - AUTHORIZE_URL: 'https://www.facebook.com/v10.0/dialog/oauth', - ACCESS_TOKEN_URL: 'https://graph.facebook.com/v10.0/dialog/oauth/access_token', -} - -export const spotify = { - AUTHORIZE_URL: 'https://accounts.spotify.com/authorize', - ACCESS_TOKEN_URL: 'https://accounts.spotify.com/api/token', - USER_INFO_URL: 'https://api.spotify.com/v1/me', -} diff --git a/src/define_config.ts b/src/define_config.ts index 3a45ff4..3810c84 100644 --- a/src/define_config.ts +++ b/src/define_config.ts @@ -7,10 +7,42 @@ * file that was distributed with this source code. */ +import { configProvider } from '@adonisjs/core' import type { HttpContext } from '@adonisjs/core/http' +import type { ConfigProvider } from '@adonisjs/core/types' -import type { AllyDriversList } from './types.js' -import allyDriversCollection from './drivers_collection.js' +import type { GoogleDriver } from './drivers/google.js' +import type { GithubDriver } from './drivers/github.js' +import type { SpotifyDriver } from './drivers/spotify.js' +import type { TwitterDriver } from './drivers/twitter.js' +import type { DiscordDriver } from './drivers/discord.js' +import type { FacebookDriver } from './drivers/facebook.js' +import type { LinkedInDriver } from './drivers/linked_in.js' +import type { + GoogleDriverConfig, + GithubDriverConfig, + SpotifyDriverConfig, + DiscordDriverConfig, + TwitterDriverConfig, + LinkedInDriverConfig, + FacebookDriverConfig, + AllyManagerDriverFactory, +} from '@adonisjs/ally/types' + +/** + * Shape of config after it has been resolved from + * the config provider + */ +type ResolvedConfig< + KnownSocialProviders extends Record< + string, + AllyManagerDriverFactory | ConfigProvider + >, +> = { + [K in keyof KnownSocialProviders]: KnownSocialProviders[K] extends ConfigProvider + ? A + : KnownSocialProviders[K] +} /** * Define config for the ally @@ -18,49 +50,78 @@ import allyDriversCollection from './drivers_collection.js' export function defineConfig< KnownSocialProviders extends Record< string, - { - [K in keyof AllyDriversList]: { driver: K } & Parameters[0] - }[keyof AllyDriversList] + AllyManagerDriverFactory | ConfigProvider >, ->( - config: KnownSocialProviders -): { - services: { - [K in keyof KnownSocialProviders]: ( - ctx: HttpContext - ) => ReturnType - } - driversInUse: Set -} { - /** - * Converting user defined config to an object of services - * that can be injected into the AllyManager class - */ - const driversInUse: Set = new Set() - - const services = Object.keys(config).reduce( - (result, provider: keyof KnownSocialProviders) => { - const providerConfig = config[provider] - driversInUse.add(providerConfig.driver) +>(config: KnownSocialProviders): ConfigProvider> { + return configProvider.create(async (app) => { + const serviceNames = Object.keys(config) + const services = {} as Record - result[provider] = (ctx: HttpContext) => { - return allyDriversCollection.create( - providerConfig.driver, - providerConfig, - ctx - ) + for (let serviceName of serviceNames) { + const service = config[serviceName] + if (typeof service === 'function') { + services[serviceName] = service + } else { + services[serviceName] = await service.resolver(app) } - return result - }, - {} as { - [K in keyof KnownSocialProviders]: ( - ctx: HttpContext - ) => ReturnType } - ) - return { - services, - driversInUse, - } + return services as ResolvedConfig + }) +} + +/** + * Helpers to configure social auth services + */ +export const services: { + discord: (config: DiscordDriverConfig) => ConfigProvider<(ctx: HttpContext) => DiscordDriver> + facebook: (config: FacebookDriverConfig) => ConfigProvider<(ctx: HttpContext) => FacebookDriver> + github: (config: GithubDriverConfig) => ConfigProvider<(ctx: HttpContext) => GithubDriver> + google: (config: GoogleDriverConfig) => ConfigProvider<(ctx: HttpContext) => GoogleDriver> + linkedin: (config: LinkedInDriverConfig) => ConfigProvider<(ctx: HttpContext) => LinkedInDriver> + spotify: (config: SpotifyDriverConfig) => ConfigProvider<(ctx: HttpContext) => SpotifyDriver> + twitter: (config: TwitterDriverConfig) => ConfigProvider<(ctx: HttpContext) => TwitterDriver> +} = { + discord(config) { + return configProvider.create(async () => { + const { DiscordDriver } = await import('./drivers/discord.js') + return (ctx) => new DiscordDriver(ctx, config) + }) + }, + facebook(config) { + return configProvider.create(async () => { + const { FacebookDriver } = await import('./drivers/facebook.js') + return (ctx) => new FacebookDriver(ctx, config) + }) + }, + github(config) { + return configProvider.create(async () => { + const { GithubDriver } = await import('./drivers/github.js') + return (ctx) => new GithubDriver(ctx, config) + }) + }, + google(config) { + return configProvider.create(async () => { + const { GoogleDriver } = await import('./drivers/google.js') + return (ctx) => new GoogleDriver(ctx, config) + }) + }, + linkedin(config) { + return configProvider.create(async () => { + const { LinkedInDriver } = await import('./drivers/linked_in.js') + return (ctx) => new LinkedInDriver(ctx, config) + }) + }, + spotify(config) { + return configProvider.create(async () => { + const { SpotifyDriver } = await import('./drivers/spotify.js') + return (ctx) => new SpotifyDriver(ctx, config) + }) + }, + twitter(config) { + return configProvider.create(async () => { + const { TwitterDriver } = await import('./drivers/twitter.js') + return (ctx) => new TwitterDriver(ctx, config) + }) + }, } diff --git a/src/drivers/discord.ts b/src/drivers/discord.ts index 4e88594..2866d5b 100644 --- a/src/drivers/discord.ts +++ b/src/drivers/discord.ts @@ -8,12 +8,13 @@ */ import type { HttpContext } from '@adonisjs/core/http' -import { +import type { HttpClient } from '@poppinss/oauth-client' + +import type { DiscordScopes, DiscordToken, ApiRequestContract, DiscordDriverConfig, - DiscordDriverContract, RedirectRequestContract, } from '../types.js' import { Oauth2Driver } from '../abstract_drivers/oauth2.js' @@ -21,10 +22,7 @@ import { Oauth2Driver } from '../abstract_drivers/oauth2.js' /** * Discord driver to login user via Discord */ -export class DiscordDriver - extends Oauth2Driver - implements DiscordDriverContract -{ +export class DiscordDriver extends Oauth2Driver { protected accessTokenUrl = 'https://discord.com/api/oauth2/token' protected authorizeUrl = 'https://discord.com/api/oauth2/authorize' protected userInfoUrl = 'https://discord.com/api/users/@me' @@ -117,7 +115,7 @@ export class DiscordDriver /** * Returns the HTTP request with the authorization header set */ - protected getAuthenticatedRequest(url: string, token: string) { + protected getAuthenticatedRequest(url: string, token: string): HttpClient { const request = this.httpClient(url) request.header('Authorization', `Bearer ${token}`) request.header('Accept', 'application/json') diff --git a/src/drivers/facebook.ts b/src/drivers/facebook.ts index 1ba6851..bae4224 100644 --- a/src/drivers/facebook.ts +++ b/src/drivers/facebook.ts @@ -7,15 +7,15 @@ * file that was distributed with this source code. */ +import type { HttpClient } from '@poppinss/oauth-client' import type { HttpContext } from '@adonisjs/core/http' -import { +import type { FacebookToken, FacebookScopes, LiteralStringUnion, ApiRequestContract, FacebookDriverConfig, FacebookProfileFields, - FacebookDriverContract, RedirectRequestContract, } from '../types.js' import { Oauth2Driver } from '../abstract_drivers/oauth2.js' @@ -23,10 +23,7 @@ import { Oauth2Driver } from '../abstract_drivers/oauth2.js' /** * Facebook driver to login user via Facebook */ -export class FacebookDriver - extends Oauth2Driver - implements FacebookDriverContract -{ +export class FacebookDriver extends Oauth2Driver { protected accessTokenUrl = 'https://graph.facebook.com/v10.0/oauth/access_token' protected authorizeUrl = 'https://www.facebook.com/v10.0/dialog/oauth' protected userInfoUrl = 'https://graph.facebook.com/v10.0/me' @@ -114,7 +111,7 @@ export class FacebookDriver /** * Returns the HTTP request with the authorization header set */ - protected getAuthenticatedRequest(url: string, token: string) { + protected getAuthenticatedRequest(url: string, token: string): HttpClient { const request = this.httpClient(url) request.header('Authorization', `Bearer ${token}`) request.header('Accept', 'application/json') diff --git a/src/drivers/github.ts b/src/drivers/github.ts index f6854ac..4e5129b 100644 --- a/src/drivers/github.ts +++ b/src/drivers/github.ts @@ -8,13 +8,13 @@ */ import type { HttpContext } from '@adonisjs/core/http' -import { +import type { HttpClient } from '@poppinss/oauth-client' +import type { GithubToken, GithubScopes, AllyUserContract, GithubDriverConfig, ApiRequestContract, - GithubDriverContract, RedirectRequestContract, } from '../types.js' import { Oauth2Driver } from '../abstract_drivers/oauth2.js' @@ -22,10 +22,7 @@ import { Oauth2Driver } from '../abstract_drivers/oauth2.js' /** * Github driver to login user via Github */ -export class GithubDriver - extends Oauth2Driver - implements GithubDriverContract -{ +export class GithubDriver extends Oauth2Driver { protected accessTokenUrl = 'https://github.com/login/oauth/access_token' protected authorizeUrl = 'https://github.com/login/oauth/authorize' protected userInfoUrl = 'https://api.github.com/user' @@ -119,7 +116,7 @@ export class GithubDriver /** * Returns the HTTP request with the authorization header set */ - protected getAuthenticatedRequest(url: string, token: string) { + protected getAuthenticatedRequest(url: string, token: string): HttpClient { const request = this.httpClient(url) request.header('Authorization', `token ${token}`) request.header('Accept', 'application/json') diff --git a/src/drivers/google.ts b/src/drivers/google.ts index 1283547..608d8ca 100644 --- a/src/drivers/google.ts +++ b/src/drivers/google.ts @@ -8,12 +8,12 @@ */ import type { HttpContext } from '@adonisjs/core/http' -import { +import type { HttpClient } from '@poppinss/oauth-client' +import type { GoogleToken, GoogleScopes, GoogleDriverConfig, ApiRequestContract, - GoogleDriverContract, RedirectRequestContract, } from '../types.js' import { Oauth2Driver } from '../abstract_drivers/oauth2.js' @@ -59,10 +59,7 @@ const SCOPE_PREFIXES = { /** * Google driver to login user via Google */ -export class GoogleDriver - extends Oauth2Driver - implements GoogleDriverContract -{ +export class GoogleDriver extends Oauth2Driver { protected accessTokenUrl = 'https://oauth2.googleapis.com/token' protected authorizeUrl = 'https://accounts.google.com/o/oauth2/v2/auth' protected userInfoUrl = 'https://www.googleapis.com/oauth2/v3/userinfo' @@ -146,7 +143,7 @@ export class GoogleDriver /** * Returns the HTTP request with the authorization header set */ - protected getAuthenticatedRequest(url: string, token: string) { + protected getAuthenticatedRequest(url: string, token: string): HttpClient { const request = this.httpClient(url) request.header('Authorization', `Bearer ${token}`) request.header('Accept', 'application/json') diff --git a/src/drivers/linked_in.ts b/src/drivers/linked_in.ts index c32a51b..59be455 100644 --- a/src/drivers/linked_in.ts +++ b/src/drivers/linked_in.ts @@ -9,12 +9,12 @@ import { Exception } from '@poppinss/utils' import type { HttpContext } from '@adonisjs/core/http' -import { +import type { HttpClient } from '@poppinss/oauth-client' +import type { LinkedInToken, LinkedInScopes, ApiRequestContract, LinkedInDriverConfig, - LinkedInDriverContract, RedirectRequestContract, } from '../types.js' import { Oauth2Driver } from '../abstract_drivers/oauth2.js' @@ -22,10 +22,7 @@ import { Oauth2Driver } from '../abstract_drivers/oauth2.js' /** * LinkedIn driver to login user via LinkedIn */ -export class LinkedInDriver - extends Oauth2Driver - implements LinkedInDriverContract -{ +export class LinkedInDriver extends Oauth2Driver { protected accessTokenUrl = 'https://www.linkedin.com/oauth/v2/accessToken' protected authorizeUrl = 'https://www.linkedin.com/oauth/v2/authorization' protected userInfoUrl = 'https://api.linkedin.com/v2/me' @@ -92,7 +89,7 @@ export class LinkedInDriver /** * Returns the HTTP request with the authorization header set */ - protected getAuthenticatedRequest(url: string, token: string) { + protected getAuthenticatedRequest(url: string, token: string): HttpClient { const request = this.httpClient(url) request.header('Authorization', `Bearer ${token}`) request.header('Accept', 'application/json') diff --git a/src/drivers/spotify.ts b/src/drivers/spotify.ts index b2c4841..e1aec95 100644 --- a/src/drivers/spotify.ts +++ b/src/drivers/spotify.ts @@ -8,12 +8,12 @@ */ import type { HttpContext } from '@adonisjs/core/http' -import { +import type { HttpClient } from '@poppinss/oauth-client' +import type { SpotifyScopes, SpotifyToken, ApiRequestContract, SpotifyDriverConfig, - SpotifyDriverContract, RedirectRequestContract, } from '../types.js' import { Oauth2Driver } from '../abstract_drivers/oauth2.js' @@ -21,10 +21,7 @@ import { Oauth2Driver } from '../abstract_drivers/oauth2.js' /** * Spotify driver to login user via Spotify */ -export class SpotifyDriver - extends Oauth2Driver - implements SpotifyDriverContract -{ +export class SpotifyDriver extends Oauth2Driver { protected accessTokenUrl = 'https://accounts.spotify.com/api/token' protected authorizeUrl = 'https://accounts.spotify.com/authorize' protected userInfoUrl = 'https://api.spotify.com/v1/me' @@ -96,7 +93,7 @@ export class SpotifyDriver /** * Returns the HTTP request with the authorization header set */ - protected getAuthenticatedRequest(url: string, token: string) { + protected getAuthenticatedRequest(url: string, token: string): HttpClient { const request = this.httpClient(url) request.header('Authorization', `Bearer ${token}`) request.header('Accept', 'application/json') diff --git a/src/drivers/twitter.ts b/src/drivers/twitter.ts index b8c03b9..ab83fb3 100644 --- a/src/drivers/twitter.ts +++ b/src/drivers/twitter.ts @@ -13,17 +13,13 @@ import { AllyUserContract, ApiRequestContract, TwitterDriverConfig, - TwitterDriverContract, } from '../types.js' import { Oauth1Driver } from '../abstract_drivers/oauth1.js' /** * Twitter driver to login user via twitter */ -export class TwitterDriver - extends Oauth1Driver - implements TwitterDriverContract -{ +export class TwitterDriver extends Oauth1Driver { protected requestTokenUrl = 'https://api.twitter.com/oauth/request_token' protected authorizeUrl = 'https://api.twitter.com/oauth/authenticate' protected accessTokenUrl = 'https://api.twitter.com/oauth/access_token' diff --git a/src/drivers_collection.ts b/src/drivers_collection.ts deleted file mode 100644 index c7aef0f..0000000 --- a/src/drivers_collection.ts +++ /dev/null @@ -1,97 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) AdonisJS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { RuntimeException } from '@poppinss/utils' -import type { HttpContext } from '@adonisjs/core/http' -import type { AllyDriversList } from './types.js' -import debug from './debug.js' - -/** - * A global collection of ally drivers. - */ -class AllyDriversCollection { - async registerBundledDrivers(drivers: Set) { - debug('drivers in use %O', drivers) - - if (drivers.has('discord') && !this.list['discord']) { - const { DiscordDriver } = await import('../src/drivers/discord.js') - this.extend('discord', (config, ctx) => new DiscordDriver(ctx, config)) - } - - if (drivers.has('facebook') && !this.list['facebook']) { - const { FacebookDriver } = await import('../src/drivers/facebook.js') - this.extend('facebook', (config, ctx) => new FacebookDriver(ctx, config)) - } - - if (drivers.has('github') && !this.list['github']) { - const { GithubDriver } = await import('../src/drivers/github.js') - this.extend('github', (config, ctx) => new GithubDriver(ctx, config)) - } - - if (drivers.has('google') && !this.list['google']) { - const { GoogleDriver } = await import('../src/drivers/google.js') - this.extend('google', (config, ctx) => new GoogleDriver(ctx, config)) - } - - if (drivers.has('linkedin') && !this.list['linkedin']) { - const { LinkedInDriver } = await import('../src/drivers/linked_in.js') - this.extend('linkedin', (config, ctx) => new LinkedInDriver(ctx, config)) - } - - if (drivers.has('spotify') && !this.list['spotify']) { - const { SpotifyDriver } = await import('../src/drivers/spotify.js') - this.extend('spotify', (config, ctx) => new SpotifyDriver(ctx, config)) - } - - if (drivers.has('twitter') && !this.list['twitter']) { - const { TwitterDriver } = await import('../src/drivers/twitter.js') - this.extend('twitter', (config, ctx) => new TwitterDriver(ctx, config)) - } - } - - /** - * List of registered drivers - */ - list: Partial = {} - - /** - * Extend drivers collection and add a custom - * driver to it. - */ - extend( - driverName: Name, - factoryCallback: AllyDriversList[Name] - ): this { - debug('registering %s driver', driverName) - this.list[driverName] = factoryCallback - return this - } - - /** - * Creates the driver instance with config - */ - create( - name: Name, - config: Parameters[0], - ctx: HttpContext - ): ReturnType { - const driverFactory = this.list[name] - if (!driverFactory) { - throw new RuntimeException( - `Unknown ally driver "${String(name)}". Make sure the driver is registered` - ) - } - - debug('creating instance of %s driver', name) - return driverFactory(config as any, ctx) as ReturnType - } -} - -const allyDriversCollection = new AllyDriversCollection() -export default allyDriversCollection diff --git a/src/types.ts b/src/types.ts index 180bbbc..d23ec54 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,6 +8,7 @@ */ import { HttpContext } from '@adonisjs/core/http' +import { ConfigProvider } from '@adonisjs/core/types' import { Oauth2AccessToken, Oauth1RequestToken, @@ -17,6 +18,7 @@ import { ApiRequestContract, RedirectRequestContract as ClientRequestContract, } from '@poppinss/oauth-client/types' +import { AllyManager } from './ally_manager.js' export type { Oauth2AccessToken } export type { Oauth1AccessToken } @@ -157,6 +159,12 @@ export interface AllyDriverContract< ): Promise> } +/** + * The manager driver factory method is called by the AllyManager to create + * an instance of a driver during an HTTP request + */ +export type AllyManagerDriverFactory = (ctx: HttpContext) => AllyDriverContract + /** * ---------------------------------------- * Discord driver @@ -216,10 +224,6 @@ export type DiscordDriverConfig = Oauth2ClientConfig & { permissions?: number } -export interface DiscordDriverContract extends AllyDriverContract { - version: 'oauth2' -} - /** * ---------------------------------------- * Github driver @@ -284,10 +288,6 @@ export type GithubDriverConfig = Oauth2ClientConfig & { userEmailUrl?: string } -export interface GithubDriverContract extends AllyDriverContract { - version: 'oauth2' -} - /** * ---------------------------------------- * Twitter driver @@ -311,10 +311,6 @@ export type TwitterDriverConfig = Oauth1ClientConfig & { userInfoUrl?: string } -export interface TwitterDriverContract extends AllyDriverContract { - version: 'oauth1' -} - /** * ---------------------------------------- * Google driver @@ -392,10 +388,6 @@ export type GoogleDriverConfig = Oauth2ClientConfig & { display?: 'page' | 'popup' | 'touch' | 'wrap' } -export interface GoogleDriverContract extends AllyDriverContract { - version: 'oauth2' -} - /** * ---------------------------------------- * LinkedIn driver @@ -441,10 +433,6 @@ export type LinkedInDriverConfig = Oauth2ClientConfig & { scopes?: LiteralStringUnion[] } -export interface LinkedInDriverContract extends AllyDriverContract { - version: 'oauth2' -} - /** * ---------------------------------------- * Facebook driver @@ -540,10 +528,6 @@ export type FacebookDriverConfig = Oauth2ClientConfig & { authType?: string } -export interface FacebookDriverContract extends AllyDriverContract { - version: 'oauth2' -} - /** * ---------------------------------------- * Spotify driver @@ -593,39 +577,23 @@ export type SpotifyDriverConfig = Oauth2ClientConfig & { scopes?: LiteralStringUnion[] showDialog?: boolean } - -export interface SpotifyDriverContract extends AllyDriverContract { - version: 'oauth2' -} - /** * END OF DRIVERS */ -/** - * List of known ally drivers. The list can be extended using - * declaration merging - */ -export interface AllyDriversList { - discord: (config: DiscordDriverConfig, ctx: HttpContext) => DiscordDriverContract - facebook: (config: FacebookDriverConfig, ctx: HttpContext) => FacebookDriverContract - github: (config: GithubDriverConfig, ctx: HttpContext) => GithubDriverContract - google: (config: GoogleDriverConfig, ctx: HttpContext) => GoogleDriverContract - linkedin: (config: LinkedInDriverConfig, ctx: HttpContext) => LinkedInDriverContract - spotify: (config: SpotifyDriverConfig, ctx: HttpContext) => SpotifyDriverContract - twitter: (config: TwitterDriverConfig, ctx: HttpContext) => TwitterDriverContract -} - -/** - * The manager driver factory method is called by the AllyManager to create - * an instance of a driver during an HTTP request - */ -export type AllyManagerDriverFactory = (ctx: HttpContext) => AllyDriverContract - /** * Social providers are inferred inside the user application * from the config file */ export interface SocialProviders {} -export type InferSocialProviders }> = - T['services'] +export type InferSocialProviders< + T extends ConfigProvider>, +> = Awaited> + +/** + * Ally service is shared with the HTTP context + */ +export interface AllyService + extends AllyManager< + SocialProviders extends Record ? SocialProviders : never + > {} diff --git a/stubs/config.stub b/stubs/config.stub index b49cb87..d5ebfdd 100644 --- a/stubs/config.stub +++ b/stubs/config.stub @@ -2,16 +2,15 @@ exports({ to: app.configPath('ally.ts') }) }}} import env from '#start/env' -import { defineConfig } from '@adonisjs/ally' +import { defineConfig, services } from '@adonisjs/ally' const allyConfig = defineConfig({ {{#each providers as provider}} - {{provider.provider}}: { - driver: '{{provider.provider}}', + {{provider.provider}}: services.{{provider.provider}}({ clientId: env.get('{{provider.envPrefix}}_CLIENT_ID'), clientSecret: env.get('{{provider.envPrefix}}_CLIENT_SECRET'), callbackUrl: '', - }, + }), {{/each}} }) diff --git a/tests/ally_manager.spec.ts b/tests/ally_manager.spec.ts index ac52f0d..2e95920 100644 --- a/tests/ally_manager.spec.ts +++ b/tests/ally_manager.spec.ts @@ -12,15 +12,8 @@ import { HttpContextFactory } from '@adonisjs/core/factories/http' import { AllyManager } from '../src/ally_manager.js' import { GithubDriver } from '../src/drivers/github.js' -import allyDriversCollection from '../src/drivers_collection.js' - -test.group('Ally manager', (group) => { - group.each.setup(() => { - return () => { - allyDriversCollection.list = {} - } - }) +test.group('Ally manager', () => { test('create an instance of a driver', ({ assert, expectTypeOf }) => { const ctx = new HttpContextFactory().create() diff --git a/tests/ally_provider.spec.ts b/tests/ally_provider.spec.ts index 4c11bf2..a0fe299 100644 --- a/tests/ally_provider.spec.ts +++ b/tests/ally_provider.spec.ts @@ -12,69 +12,19 @@ import { IgnitorFactory } from '@adonisjs/core/factories' import { HttpContextFactory } from '@adonisjs/core/factories/http' import { AllyManager } from '../src/ally_manager.js' -import { defineConfig } from '../src/define_config.js' -import allyDriversCollection from '../src/drivers_collection.js' +import { defineConfig, services } from '../src/define_config.js' const BASE_URL = new URL('./tmp/', import.meta.url) const IMPORTER = (filePath: string) => { - if (filePath.startsWith('./') || filePath.startsWith('../')) { - return import(new URL(filePath, BASE_URL).href) - } return import(filePath) } -test.group('Ally provider', (group) => { - group.each.setup(() => { - return () => { - allyDriversCollection.list = {} - } - }) - - test('register drivers in use', async ({ assert }) => { - const ignitor = new IgnitorFactory() - .merge({ - rcFileContents: { - providers: ['../../providers/ally_provider.js'], - }, - }) - .withCoreConfig() - .withCoreProviders() - .merge({ - config: { - ally: defineConfig({ - github: { - driver: 'github', - clientId: '', - clientSecret: '', - callbackUrl: '', - }, - }), - }, - }) - .create(BASE_URL, { - importer: IMPORTER, - }) - - const app = ignitor.createApp('web') - await app.init() - await app.boot() - - assert.property(allyDriversCollection.list, 'github') - assert.notAllProperties(allyDriversCollection.list, [ - 'facebook', - 'linkedin', - 'google', - 'spotify', - 'twitter', - 'discord', - ]) - }) - - test('add ally to HttpContext', async ({ assert }) => { +test.group('Ally provider', () => { + test('define HttpContext.ally property', async ({ assert }) => { const ignitor = new IgnitorFactory() .merge({ rcFileContents: { - providers: ['../../providers/ally_provider.js'], + providers: [() => import('../providers/ally_provider.js')], }, }) .withCoreConfig() @@ -82,12 +32,11 @@ test.group('Ally provider', (group) => { .merge({ config: { ally: defineConfig({ - github: { - driver: 'github', + github: services.github({ clientId: '', clientSecret: '', callbackUrl: '', - }, + }), }), }, }) diff --git a/tests/configure.spec.ts b/tests/configure.spec.ts index 88dd5ed..9de185f 100644 --- a/tests/configure.spec.ts +++ b/tests/configure.spec.ts @@ -56,21 +56,19 @@ test.group('Configure', (group) => { await assert.fileContains('config/ally.ts', `declare module '@adonisjs/ally/types' {`) await assert.fileContains( 'config/ally.ts', - `github: { - driver: 'github', + `github: services.github({ clientId: env.get('GITHUB_CLIENT_ID'), clientSecret: env.get('GITHUB_CLIENT_SECRET'), callbackUrl: '', - },` + }),` ) await assert.fileContains( 'config/ally.ts', - `linkedin: { - driver: 'linkedin', + `linkedin: services.linkedin({ clientId: env.get('LINKEDIN_CLIENT_ID'), clientSecret: env.get('LINKEDIN_CLIENT_SECRET'), callbackUrl: '', - },` + }),` ) await assert.fileContains('.env', 'GITHUB_CLIENT_ID') await assert.fileContains('.env', 'GITHUB_CLIENT_SECRET') @@ -81,5 +79,5 @@ test.group('Configure', (group) => { await assert.fileContains('start/env.ts', 'GITHUB_CLIENT_SECRET: Env.schema.string()') await assert.fileContains('start/env.ts', 'LINKEDIN_CLIENT_ID: Env.schema.string()') await assert.fileContains('start/env.ts', 'LINKEDIN_CLIENT_SECRET: Env.schema.string()') - }).timeout(6000) + }).timeout(1000 * 60) }) diff --git a/tests/define_config.spec.ts b/tests/define_config.spec.ts index 791bd54..3862c12 100644 --- a/tests/define_config.spec.ts +++ b/tests/define_config.spec.ts @@ -8,39 +8,174 @@ */ import { test } from '@japa/runner' +import { AppFactory } from '@adonisjs/core/factories/app' +import { ApplicationService } from '@adonisjs/core/types' import { HttpContextFactory } from '@adonisjs/core/factories/http' -import { AllyManager, defineConfig } from '../index.js' +import { AllyManager } from '../src/ally_manager.js' +import { GoogleDriver } from '../src/drivers/google.js' import { GithubDriver } from '../src/drivers/github.js' -import type { GithubDriverContract } from '../src/types.js' -import allyDriversCollection from '../src/drivers_collection.js' +import { services, defineConfig } from '../src/define_config.js' +import { DiscordDriver } from '../src/drivers/discord.js' +import { FacebookDriver } from '../src/drivers/facebook.js' +import { LinkedInDriver } from '../src/drivers/linked_in.js' +import { SpotifyDriver } from '../src/drivers/spotify.js' +import { TwitterDriver } from '../src/drivers/twitter.js' -test.group('Define config', (group) => { - group.each.setup(() => { - return () => { - allyDriversCollection.list = {} - } - }) +const BASE_URL = new URL('./', import.meta.url) +const app = new AppFactory().create(BASE_URL, () => {}) as ApplicationService - test('define manager config from user defined config', async ({ assert, expectTypeOf }) => { - const managerConfig = defineConfig({ - github: { - driver: 'github', +test.group('Define config', () => { + test('transform user defined config', async ({ assert, expectTypeOf }) => { + const managerConfig = await defineConfig({ + github: services.github({ clientId: '', clientSecret: '', callbackUrl: '', scopes: ['admin:org'], - }, - }) + }), + }).resolver(app) const ctx = new HttpContextFactory().create() - const ally = new AllyManager(managerConfig.services, ctx) + const ally = new AllyManager(managerConfig, ctx) - await allyDriversCollection.registerBundledDrivers(managerConfig.driversInUse) + assert.instanceOf(ally.use('github'), GithubDriver) + assert.strictEqual(ally.use('github'), ally.use('github')) + expectTypeOf(ally.use).parameters.toEqualTypeOf<['github']>() + expectTypeOf(ally.use('github')).toMatchTypeOf() + }) +}) + +test.group('Config services', () => { + test('configure github driver', async ({ assert, expectTypeOf }) => { + const managerConfig = await defineConfig({ + github: services.github({ + clientId: '', + clientSecret: '', + callbackUrl: '', + scopes: ['admin:org'], + }), + }).resolver(app) + + const ctx = new HttpContextFactory().create() + const ally = new AllyManager(managerConfig, ctx) assert.instanceOf(ally.use('github'), GithubDriver) assert.strictEqual(ally.use('github'), ally.use('github')) expectTypeOf(ally.use).parameters.toEqualTypeOf<['github']>() - expectTypeOf(ally.use('github')).toMatchTypeOf() + expectTypeOf(ally.use('github')).toMatchTypeOf() + }) + + test('configure google driver', async ({ assert, expectTypeOf }) => { + const managerConfig = await defineConfig({ + google: services.google({ + clientId: '', + clientSecret: '', + callbackUrl: '', + scopes: ['admin:org'], + }), + }).resolver(app) + + const ctx = new HttpContextFactory().create() + const ally = new AllyManager(managerConfig, ctx) + + assert.instanceOf(ally.use('google'), GoogleDriver) + assert.strictEqual(ally.use('google'), ally.use('google')) + expectTypeOf(ally.use).parameters.toEqualTypeOf<['google']>() + expectTypeOf(ally.use('google')).toMatchTypeOf() + }) + + test('configure discord driver', async ({ assert, expectTypeOf }) => { + const managerConfig = await defineConfig({ + discord: services.discord({ + clientId: '', + clientSecret: '', + callbackUrl: '', + scopes: ['admin:org'], + }), + }).resolver(app) + + const ctx = new HttpContextFactory().create() + const ally = new AllyManager(managerConfig, ctx) + + assert.instanceOf(ally.use('discord'), DiscordDriver) + assert.strictEqual(ally.use('discord'), ally.use('discord')) + expectTypeOf(ally.use).parameters.toEqualTypeOf<['discord']>() + expectTypeOf(ally.use('discord')).toMatchTypeOf() + }) + + test('configure facebook driver', async ({ assert, expectTypeOf }) => { + const managerConfig = await defineConfig({ + facebook: services.facebook({ + clientId: '', + clientSecret: '', + callbackUrl: '', + scopes: ['admin:org'], + }), + }).resolver(app) + + const ctx = new HttpContextFactory().create() + const ally = new AllyManager(managerConfig, ctx) + + assert.instanceOf(ally.use('facebook'), FacebookDriver) + assert.strictEqual(ally.use('facebook'), ally.use('facebook')) + expectTypeOf(ally.use).parameters.toEqualTypeOf<['facebook']>() + expectTypeOf(ally.use('facebook')).toMatchTypeOf() + }) + + test('configure linkedin driver', async ({ assert, expectTypeOf }) => { + const managerConfig = await defineConfig({ + linkedin: services.linkedin({ + clientId: '', + clientSecret: '', + callbackUrl: '', + scopes: ['admin:org'], + }), + }).resolver(app) + + const ctx = new HttpContextFactory().create() + const ally = new AllyManager(managerConfig, ctx) + + assert.instanceOf(ally.use('linkedin'), LinkedInDriver) + assert.strictEqual(ally.use('linkedin'), ally.use('linkedin')) + expectTypeOf(ally.use).parameters.toEqualTypeOf<['linkedin']>() + expectTypeOf(ally.use('linkedin')).toMatchTypeOf() + }) + + test('configure spotify driver', async ({ assert, expectTypeOf }) => { + const managerConfig = await defineConfig({ + spotify: services.spotify({ + clientId: '', + clientSecret: '', + callbackUrl: '', + scopes: ['admin:org'], + }), + }).resolver(app) + + const ctx = new HttpContextFactory().create() + const ally = new AllyManager(managerConfig, ctx) + + assert.instanceOf(ally.use('spotify'), SpotifyDriver) + assert.strictEqual(ally.use('spotify'), ally.use('spotify')) + expectTypeOf(ally.use).parameters.toEqualTypeOf<['spotify']>() + expectTypeOf(ally.use('spotify')).toMatchTypeOf() + }) + + test('configure twitter driver', async ({ assert, expectTypeOf }) => { + const managerConfig = await defineConfig({ + twitter: services.twitter({ + clientId: '', + clientSecret: '', + callbackUrl: '', + }), + }).resolver(app) + + const ctx = new HttpContextFactory().create() + const ally = new AllyManager(managerConfig, ctx) + + assert.instanceOf(ally.use('twitter'), TwitterDriver) + assert.strictEqual(ally.use('twitter'), ally.use('twitter')) + expectTypeOf(ally.use).parameters.toEqualTypeOf<['twitter']>() + expectTypeOf(ally.use('twitter')).toMatchTypeOf() }) }) diff --git a/tests/drivers_collection.spec.ts b/tests/drivers_collection.spec.ts deleted file mode 100644 index f6434ca..0000000 --- a/tests/drivers_collection.spec.ts +++ /dev/null @@ -1,116 +0,0 @@ -/* - * @adonisjs/ally - * - * (c) AdonisJS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { HttpContextFactory } from '@adonisjs/core/factories/http' - -import { GithubDriver } from '../src/drivers/github.js' -import { GoogleDriver } from '../src/drivers/google.js' -import { TwitterDriver } from '../src/drivers/twitter.js' -import { DiscordDriver } from '../src/drivers/discord.js' -import { SpotifyDriver } from '../src/drivers/spotify.js' -import { FacebookDriver } from '../src/drivers/facebook.js' -import { LinkedInDriver } from '../src/drivers/linked_in.js' -import allyDriversCollection from '../src/drivers_collection.js' -import type { - DiscordDriverContract, - FacebookDriverContract, - GithubDriverContract, - GoogleDriverContract, - LinkedInDriverContract, - SpotifyDriverContract, - TwitterDriverContract, -} from '../src/types.js' - -test.group('Drivers Collection', (group) => { - group.each.setup(() => { - return () => { - allyDriversCollection.list = {} - } - }) - - test('create an instance of a known driver', async ({ assert, expectTypeOf }) => { - const ctx = new HttpContextFactory().create() - await allyDriversCollection.registerBundledDrivers( - new Set(['github', 'google', 'discord', 'facebook', 'linkedin', 'spotify', 'twitter']) - ) - - const discord = allyDriversCollection.create( - 'discord', - { clientId: '', clientSecret: '', callbackUrl: '' }, - ctx - ) - assert.instanceOf(discord, DiscordDriver) - expectTypeOf(discord).toEqualTypeOf() - - const github = allyDriversCollection.create( - 'github', - { clientId: '', clientSecret: '', callbackUrl: '' }, - ctx - ) - assert.instanceOf(github, GithubDriver) - expectTypeOf(github).toEqualTypeOf() - - const google = allyDriversCollection.create( - 'google', - { clientId: '', clientSecret: '', callbackUrl: '' }, - ctx - ) - assert.instanceOf(google, GoogleDriver) - expectTypeOf(google).toEqualTypeOf() - - const facebook = allyDriversCollection.create( - 'facebook', - { clientId: '', clientSecret: '', callbackUrl: '' }, - ctx - ) - assert.instanceOf(facebook, FacebookDriver) - expectTypeOf(facebook).toEqualTypeOf() - - const linkedin = allyDriversCollection.create( - 'linkedin', - { clientId: '', clientSecret: '', callbackUrl: '' }, - ctx - ) - assert.instanceOf(linkedin, LinkedInDriver) - expectTypeOf(linkedin).toEqualTypeOf() - - const spotify = allyDriversCollection.create( - 'spotify', - { clientId: '', clientSecret: '', callbackUrl: '' }, - ctx - ) - assert.instanceOf(spotify, SpotifyDriver) - expectTypeOf(spotify).toEqualTypeOf() - - const twitter = allyDriversCollection.create( - 'twitter', - { clientId: '', clientSecret: '', callbackUrl: '' }, - ctx - ) - assert.instanceOf(twitter, TwitterDriver) - expectTypeOf(twitter).toEqualTypeOf() - }) - - test('extend drivers collection', ({ assert }) => { - class Foo {} - - allyDriversCollection.extend('foo' as any, () => { - return new Foo() - }) - - const ctx = new HttpContextFactory().create() - assert.instanceOf(allyDriversCollection.create('foo' as any, {}, ctx), Foo) - }) - - test('throw error when trying to create an unknown driver', () => { - const ctx = new HttpContextFactory().create() - allyDriversCollection.create('bar' as any, {}, ctx) - }).throws('Unknown ally driver "bar". Make sure the driver is registered') -}) From a73d9ec0fae11248ddabed86215ddeb8153b5837 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 19 Oct 2023 20:29:21 +0530 Subject: [PATCH 24/37] refactor: fix typing issues --- examples/config/ally.ts | 38 +++++++++++++++----------------------- index.ts | 2 +- package.json | 4 ++-- src/define_config.ts | 2 +- src/types.ts | 8 ++++---- tsconfig.json | 2 +- 6 files changed, 24 insertions(+), 32 deletions(-) diff --git a/examples/config/ally.ts b/examples/config/ally.ts index c4d1ff9..4cc09ee 100644 --- a/examples/config/ally.ts +++ b/examples/config/ally.ts @@ -1,52 +1,44 @@ -import { defineConfig } from '../../src/define_config.js' +import { defineConfig, services } from '../../index.js' const allyConfig = defineConfig({ - discord: { - driver: 'discord', + discord: services.discord({ clientId: process.env.DISCORD_CLIENT_ID!, clientSecret: process.env.DISCORD_CLIENT_SECRET!, callbackUrl: `http://localhost:${process.env.PORT}/discord/callback`, - }, - google: { - driver: 'google', + }), + google: services.google({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, callbackUrl: `http://localhost:${process.env.PORT}/google/callback`, - }, - github: { - driver: 'github', + }), + github: services.github({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, callbackUrl: `http://localhost:${process.env.PORT}/github/callback`, - }, - linkedin: { - driver: 'linkedin', + }), + linkedin: services.linkedin({ clientId: process.env.LINKEDIN_CLIENT_ID!, clientSecret: process.env.LINKEDIN_CLIENT_SECRET!, callbackUrl: `http://localhost:${process.env.PORT}/linkedin/callback`, - }, - twitter: { - driver: 'twitter', + }), + twitter: services.twitter({ clientId: process.env.TWITTER_API_KEY!, clientSecret: process.env.TWITTER_APP_SECRET!, callbackUrl: `http://localhost:${process.env.PORT}/twitter/callback`, - }, - facebook: { - driver: 'facebook', + }), + facebook: services.facebook({ clientId: process.env.FACEBOOK_CLIENT_ID!, clientSecret: process.env.FACEBOOK_CLIENT_SECRET!, callbackUrl: `http://localhost:${process.env.PORT}/facebook/callback`, - }, - spotify: { - driver: 'spotify', + }), + spotify: services.spotify({ clientId: process.env.SPOTIFY_CLIENT_ID!, clientSecret: process.env.SPOTIFY_CLIENT_SECRET!, callbackUrl: `http://localhost:${process.env.PORT}/spotify/callback`, - }, + }), }) declare module '@adonisjs/ally/types' { interface SocialProviders extends InferSocialProviders {} } - export default allyConfig diff --git a/index.ts b/index.ts index 1f58a55..af664d4 100644 --- a/index.ts +++ b/index.ts @@ -13,7 +13,7 @@ export * as errors from './src/errors.js' export { configure } from './configure.js' export { stubsRoot } from './stubs/main.js' export { AllyManager } from './src/ally_manager.js' -export { defineConfig } from './src/define_config.js' +export { defineConfig, services } from './src/define_config.js' export { RedirectRequest } from './src/redirect_request.js' export { Oauth1Driver } from './src/abstract_drivers/oauth1.js' diff --git a/package.json b/package.json index 9dd20da..0a99a15 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "license": "MIT", "devDependencies": { "@adonisjs/assembler": "^6.1.3-25", - "@adonisjs/core": "^6.1.5-28", + "@adonisjs/core": "^6.1.5-29", "@adonisjs/eslint-config": "^1.1.8", "@adonisjs/prettier-config": "^1.1.8", "@adonisjs/tsconfig": "^1.1.8", @@ -78,7 +78,7 @@ "@poppinss/utils": "^6.5.0" }, "peerDependencies": { - "@adonisjs/core": "^6.1.5-28" + "@adonisjs/core": "^6.1.5-29" }, "repository": { "type": "git", diff --git a/src/define_config.ts b/src/define_config.ts index 3810c84..7ae93cf 100644 --- a/src/define_config.ts +++ b/src/define_config.ts @@ -27,7 +27,7 @@ import type { LinkedInDriverConfig, FacebookDriverConfig, AllyManagerDriverFactory, -} from '@adonisjs/ally/types' +} from './types.js' /** * Shape of config after it has been resolved from diff --git a/src/types.ts b/src/types.ts index d23ec54..79089a4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,9 +7,9 @@ * file that was distributed with this source code. */ -import { HttpContext } from '@adonisjs/core/http' -import { ConfigProvider } from '@adonisjs/core/types' -import { +import type { HttpContext } from '@adonisjs/core/http' +import type { ConfigProvider } from '@adonisjs/core/types' +import type { Oauth2AccessToken, Oauth1RequestToken, Oauth1AccessToken, @@ -18,7 +18,7 @@ import { ApiRequestContract, RedirectRequestContract as ClientRequestContract, } from '@poppinss/oauth-client/types' -import { AllyManager } from './ally_manager.js' +import type { AllyManager } from './ally_manager.js' export type { Oauth2AccessToken } export type { Oauth1AccessToken } diff --git a/tsconfig.json b/tsconfig.json index 2039043..ad0cc44 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,4 +4,4 @@ "rootDir": "./", "outDir": "./build" } -} \ No newline at end of file +} From 56f910cf8a4639807af22a42f74b6d7b5405d1d5 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 19 Oct 2023 20:33:00 +0530 Subject: [PATCH 25/37] chore(release): 5.0.0-6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0a99a15..57a57a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/ally", - "version": "5.0.0-5", + "version": "5.0.0-6", "description": "Social authentication provider for AdonisJS", "type": "module", "main": "build/index.js", From 162e097a9e3f0cfa96d9ce7839d220c5ee45f1a1 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 27 Nov 2023 16:12:57 +0530 Subject: [PATCH 26/37] chore: update dependencies --- package.json | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 57a57a6..b907b86 100644 --- a/package.json +++ b/package.json @@ -47,38 +47,38 @@ "author": "adonisjs,virk", "license": "MIT", "devDependencies": { - "@adonisjs/assembler": "^6.1.3-25", - "@adonisjs/core": "^6.1.5-29", - "@adonisjs/eslint-config": "^1.1.8", - "@adonisjs/prettier-config": "^1.1.8", - "@adonisjs/tsconfig": "^1.1.8", - "@commitlint/cli": "^17.8.0", - "@commitlint/config-conventional": "^17.8.0", - "@japa/assert": "^2.0.0", + "@adonisjs/assembler": "^6.1.3-28", + "@adonisjs/core": "^6.1.5-32", + "@adonisjs/eslint-config": "^1.1.9", + "@adonisjs/prettier-config": "^1.1.9", + "@adonisjs/tsconfig": "^1.1.9", + "@commitlint/cli": "^18.4.3", + "@commitlint/config-conventional": "^18.4.3", + "@japa/assert": "^2.0.1", "@japa/expect-type": "^2.0.0", - "@japa/file-system": "^2.0.0", - "@japa/runner": "^3.0.4", - "@swc/core": "1.3.82", - "@types/node": "^20.8.7", + "@japa/file-system": "^2.0.1", + "@japa/runner": "^3.1.0", + "@swc/core": "^1.3.99", + "@types/node": "^20.10.0", "c8": "^8.0.1", "copyfiles": "^2.4.1", "del-cli": "^5.1.0", "dotenv": "^16.3.1", - "eslint": "^8.51.0", + "eslint": "^8.54.0", "github-label-sync": "^2.3.1", "husky": "^8.0.3", - "nock": "^13.3.4", + "nock": "^13.3.8", "np": "^8.0.4", - "prettier": "^3.0.3", + "prettier": "^3.1.0", "ts-node": "^10.9.1", - "typescript": "^5.2.2" + "typescript": "5.2.2" }, "dependencies": { - "@poppinss/oauth-client": "^5.1.0", - "@poppinss/utils": "^6.5.0" + "@poppinss/oauth-client": "^5.1.1", + "@poppinss/utils": "^6.5.1" }, "peerDependencies": { - "@adonisjs/core": "^6.1.5-29" + "@adonisjs/core": "^6.1.5-32" }, "repository": { "type": "git", From 3c3dd51e74b7ac6163ee00c4341cf524fe66a04e Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 27 Nov 2023 16:16:09 +0530 Subject: [PATCH 27/37] chore: use tsup for bundling --- package.json | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index b907b86..c5f0393 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,10 @@ "type": "module", "main": "build/index.js", "files": [ - "build/src", - "build/stubs", - "build/providers", - "build/index.d.ts", - "build/index.js", - "build/configure.d.ts", - "build/configure.js" + "build", + "!build/bin", + "!build/examples", + "!build/tests" ], "exports": { ".": "./build/index.js", @@ -27,9 +24,11 @@ "clean": "del-cli build", "typecheck": "tsc --noEmit", "start": "node --loader=ts-node/esm examples/app.ts", - "copy:templates": "copyfiles \"stubs/**/*.stub\" build", - "compile": "npm run lint && npm run clean && tsc", - "build": "npm run compile && npm run copy:templates", + "copy:templates": "copyfiles --up=1 \"stubs/**/*.stub\" build", + "precompile": "npm run lint && npm run clean", + "compile": "tsup-node && tsc --emitDeclarationOnly --declaration", + "postcompile": "npm run copy:templates", + "build": "npm run compile", "prepublishOnly": "npm run build", "lint": "eslint . --ext=.ts", "format": "prettier --write .", @@ -71,6 +70,7 @@ "np": "^8.0.4", "prettier": "^3.1.0", "ts-node": "^10.9.1", + "tsup": "^8.0.1", "typescript": "5.2.2" }, "dependencies": { @@ -118,5 +118,17 @@ "src/abstract_drivers/**", "stubs/**" ] + }, + "tsup": { + "entry": [ + "index.ts", + "./providers/ally_provider.ts" + ], + "outDir": "./build", + "clean": true, + "format": "esm", + "dts": false, + "sourcemap": true, + "target": "esnext" } } From efaef26b41bcae8ac14c72b5e579cc9b5a63bb2c Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 27 Nov 2023 16:17:07 +0530 Subject: [PATCH 28/37] docs(README): remove snyk badge --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 36f0e81..335e010 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
-[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] [![snyk-image]][snyk-url] +[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] ## Introduction Social authentication provider for AdonisJS. Supports *Github*, Google, Twitter, Facebook, Discord, Spotify, and LinkedIn. @@ -21,8 +21,8 @@ In order to ensure that the AdonisJS community is welcoming to all, please revie ## License AdonisJS ally is open-sourced software licensed under the [MIT license](LICENSE.md). -[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/adonisjs/ally/test.yml?style=for-the-badge -[gh-workflow-url]: https://github.com/adonisjs/ally/actions/workflows/test.yml "Github action" +[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/adonisjs/ally/checks.yml?style=for-the-badge +[gh-workflow-url]: https://github.com/adonisjs/ally/actions/workflows/checks.yml "Github action" [npm-image]: https://img.shields.io/npm/v/@adonisjs/ally/latest.svg?style=for-the-badge&logo=npm [npm-url]: https://www.npmjs.com/package/@adonisjs/ally/v/latest "npm" @@ -31,6 +31,3 @@ AdonisJS ally is open-sourced software licensed under the [MIT license](LICENSE. [license-url]: LICENSE.md [license-image]: https://img.shields.io/github/license/adonisjs/ally?style=for-the-badge - -[snyk-image]: https://img.shields.io/snyk/vulnerabilities/github/adonisjs/ally?label=Snyk%20Vulnerabilities&style=for-the-badge -[snyk-url]: https://snyk.io/test/github/adonisjs/ally?targetFile=package.json "snyk" From 963d0df24235b14ba2c5e55038761f7f1eb07634 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 27 Nov 2023 16:18:58 +0530 Subject: [PATCH 29/37] chore(release): 5.0.0-7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c5f0393..b051220 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/ally", - "version": "5.0.0-6", + "version": "5.0.0-7", "description": "Social authentication provider for AdonisJS", "type": "module", "main": "build/index.js", From 16951f77497623935cad6d295b2c5de03c9419fd Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sun, 24 Dec 2023 15:27:12 +0530 Subject: [PATCH 30/37] chore: update dependencies --- package.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index b051220..4649312 100644 --- a/package.json +++ b/package.json @@ -46,39 +46,39 @@ "author": "adonisjs,virk", "license": "MIT", "devDependencies": { - "@adonisjs/assembler": "^6.1.3-28", - "@adonisjs/core": "^6.1.5-32", - "@adonisjs/eslint-config": "^1.1.9", - "@adonisjs/prettier-config": "^1.1.9", - "@adonisjs/tsconfig": "^1.1.9", + "@adonisjs/assembler": "^7.0.0-0", + "@adonisjs/core": "^6.1.5-34", + "@adonisjs/eslint-config": "^1.2.0", + "@adonisjs/prettier-config": "^1.2.0", + "@adonisjs/tsconfig": "^1.2.0", "@commitlint/cli": "^18.4.3", "@commitlint/config-conventional": "^18.4.3", - "@japa/assert": "^2.0.1", - "@japa/expect-type": "^2.0.0", - "@japa/file-system": "^2.0.1", - "@japa/runner": "^3.1.0", - "@swc/core": "^1.3.99", - "@types/node": "^20.10.0", + "@japa/assert": "^2.1.0", + "@japa/expect-type": "^2.0.1", + "@japa/file-system": "^2.1.1", + "@japa/runner": "^3.1.1", + "@swc/core": "^1.3.101", + "@types/node": "^20.10.5", "c8": "^8.0.1", "copyfiles": "^2.4.1", "del-cli": "^5.1.0", "dotenv": "^16.3.1", - "eslint": "^8.54.0", + "eslint": "^8.56.0", "github-label-sync": "^2.3.1", "husky": "^8.0.3", - "nock": "^13.3.8", - "np": "^8.0.4", - "prettier": "^3.1.0", - "ts-node": "^10.9.1", + "nock": "^13.4.0", + "np": "^9.2.0", + "prettier": "^3.1.1", + "ts-node": "^10.9.2", "tsup": "^8.0.1", - "typescript": "5.2.2" + "typescript": "^5.3.3" }, "dependencies": { - "@poppinss/oauth-client": "^5.1.1", - "@poppinss/utils": "^6.5.1" + "@poppinss/oauth-client": "^5.1.2", + "@poppinss/utils": "^6.7.0" }, "peerDependencies": { - "@adonisjs/core": "^6.1.5-32" + "@adonisjs/core": "^6.1.5-34" }, "repository": { "type": "git", From 20d64e00ff19b2acfa8fd6c61fa17f530f663550 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sun, 24 Dec 2023 15:41:17 +0530 Subject: [PATCH 31/37] refactor: make stubs work with the CLI flag --- configure.ts | 62 ++++++++-- index.ts | 1 - stubs/{config.stub => config/ally.stub} | 0 tests/configure.spec.ts | 150 +++++++++++++++++++++++- 4 files changed, 203 insertions(+), 10 deletions(-) rename stubs/{config.stub => config/ally.stub} (100%) diff --git a/configure.ts b/configure.ts index 6485700..3854d21 100644 --- a/configure.ts +++ b/configure.ts @@ -8,27 +8,75 @@ */ import type Configure from '@adonisjs/core/commands/configure' +import { stubsRoot } from './stubs/main.js' + +/** + * List of available providers + */ +const AVAILABLE_PROVIDERS = [ + 'discord', + 'facebook', + 'github', + 'google', + 'linkedin', + 'spotify', + 'twitter', +] /** * Configures the package */ export async function configure(command: Configure) { - const providers = await command.prompt.multiple( - 'Select the social auth providers you plan to use', - ['discord', 'facebook', 'github', 'google', 'linkedin', 'spotify', 'twitter'] - ) + /** + * Read providers from the CLI flags + */ + let selectedProviders: string[] | string | undefined = command.parsedFlags.providers + + /** + * Otherwise force prompt for selection + */ + if (!selectedProviders) { + selectedProviders = await command.prompt.multiple( + 'Select the social auth providers you plan to use', + AVAILABLE_PROVIDERS, + { + validate(value) { + return !value || !value.length + ? 'Select a social provider to configure the package' + : true + }, + } + ) + } + + /** + * Cast CLI string value to an array + */ + let providers = ( + typeof selectedProviders === 'string' ? [selectedProviders] : selectedProviders + ) as string[] + + /** + * Validate CLI selection to contain known providers + */ + const unknownProvider = providers.find((provider) => !AVAILABLE_PROVIDERS.includes(provider)) + if (unknownProvider) { + command.exitCode = 1 + command.logger.error(`Invalid social provider "${unknownProvider}"`) + return + } + + const codemods = await command.createCodemods() /** * Publish config file */ - await command.publishStub('config.stub', { + await codemods.makeUsingStub(stubsRoot, 'config/ally.stub', { providers: providers.map((provider) => { return { provider, envPrefix: provider.toUpperCase() } }), }) - const codemods = await command.createCodemods() - /** * Publish provider */ diff --git a/index.ts b/index.ts index af664d4..269ce30 100644 --- a/index.ts +++ b/index.ts @@ -11,7 +11,6 @@ export { HttpClient as ApiRequest } from '@poppinss/oauth-client' export * as errors from './src/errors.js' export { configure } from './configure.js' -export { stubsRoot } from './stubs/main.js' export { AllyManager } from './src/ally_manager.js' export { defineConfig, services } from './src/define_config.js' diff --git a/stubs/config.stub b/stubs/config/ally.stub similarity index 100% rename from stubs/config.stub rename to stubs/config/ally.stub diff --git a/tests/configure.spec.ts b/tests/configure.spec.ts index 9de185f..c8d0d29 100644 --- a/tests/configure.spec.ts +++ b/tests/configure.spec.ts @@ -20,7 +20,153 @@ test.group('Configure', (group) => { context.fs.basePath = fileURLToPath(BASE_URL) }) - test('create config file and register provider', async ({ fs, assert }) => { + group.each.disableTimeout() + + test('do not prompt when provider is provided as a CLI flag', async ({ fs, assert }) => { + const ignitor = new IgnitorFactory() + .withCoreProviders() + .withCoreConfig() + .create(BASE_URL, { + importer: (filePath) => { + if (filePath.startsWith('./') || filePath.startsWith('../')) { + return import(new URL(filePath, BASE_URL).href) + } + + return import(filePath) + }, + }) + + const app = ignitor.createApp('web') + await app.init() + await app.boot() + + await fs.create('.env', '') + await fs.createJson('tsconfig.json', {}) + await fs.create('start/env.ts', `export default Env.create(new URL('./'), {})`) + await fs.create('adonisrc.ts', `export default defineConfig({})`) + + const ace = await app.container.make('ace') + const command = await ace.create(Configure, ['../../index.js', '--providers=facebook']) + await command.exec() + + await assert.fileExists('config/ally.ts') + await assert.fileExists('adonisrc.ts') + await assert.fileContains('adonisrc.ts', '@adonisjs/ally/ally_provider') + await assert.fileContains('config/ally.ts', 'defineConfig') + await assert.fileContains('config/ally.ts', `declare module '@adonisjs/ally/types' {`) + await assert.fileContains( + 'config/ally.ts', + `facebook: services.facebook({ + clientId: env.get('FACEBOOK_CLIENT_ID'), + clientSecret: env.get('FACEBOOK_CLIENT_SECRET'), + callbackUrl: '', + }),` + ) + await assert.fileContains('.env', 'FACEBOOK_CLIENT_ID') + await assert.fileContains('.env', 'FACEBOOK_CLIENT_SECRET') + + await assert.fileContains('start/env.ts', 'FACEBOOK_CLIENT_ID: Env.schema.string()') + await assert.fileContains('start/env.ts', 'FACEBOOK_CLIENT_SECRET: Env.schema.string()') + }) + + test('define multiple providers via the CLI flag', async ({ fs, assert }) => { + const ignitor = new IgnitorFactory() + .withCoreProviders() + .withCoreConfig() + .create(BASE_URL, { + importer: (filePath) => { + if (filePath.startsWith('./') || filePath.startsWith('../')) { + return import(new URL(filePath, BASE_URL).href) + } + + return import(filePath) + }, + }) + + const app = ignitor.createApp('web') + await app.init() + await app.boot() + + await fs.create('.env', '') + await fs.createJson('tsconfig.json', {}) + await fs.create('start/env.ts', `export default Env.create(new URL('./'), {})`) + await fs.create('adonisrc.ts', `export default defineConfig({})`) + + const ace = await app.container.make('ace') + const command = await ace.create(Configure, [ + '../../index.js', + '--providers=github', + '--providers=linkedin', + ]) + await command.exec() + + await assert.fileExists('config/ally.ts') + await assert.fileExists('adonisrc.ts') + await assert.fileContains('adonisrc.ts', '@adonisjs/ally/ally_provider') + await assert.fileContains('config/ally.ts', 'defineConfig') + await assert.fileContains('config/ally.ts', `declare module '@adonisjs/ally/types' {`) + await assert.fileContains( + 'config/ally.ts', + `github: services.github({ + clientId: env.get('GITHUB_CLIENT_ID'), + clientSecret: env.get('GITHUB_CLIENT_SECRET'), + callbackUrl: '', + }),` + ) + await assert.fileContains( + 'config/ally.ts', + `linkedin: services.linkedin({ + clientId: env.get('LINKEDIN_CLIENT_ID'), + clientSecret: env.get('LINKEDIN_CLIENT_SECRET'), + callbackUrl: '', + }),` + ) + await assert.fileContains('.env', 'GITHUB_CLIENT_ID') + await assert.fileContains('.env', 'GITHUB_CLIENT_SECRET') + await assert.fileContains('.env', 'LINKEDIN_CLIENT_ID') + await assert.fileContains('.env', 'LINKEDIN_CLIENT_SECRET') + + await assert.fileContains('start/env.ts', 'GITHUB_CLIENT_ID: Env.schema.string()') + await assert.fileContains('start/env.ts', 'GITHUB_CLIENT_SECRET: Env.schema.string()') + await assert.fileContains('start/env.ts', 'LINKEDIN_CLIENT_ID: Env.schema.string()') + await assert.fileContains('start/env.ts', 'LINKEDIN_CLIENT_SECRET: Env.schema.string()') + }) + + test('report error when CLI provider is invalid', async ({ fs, assert }) => { + const ignitor = new IgnitorFactory() + .withCoreProviders() + .withCoreConfig() + .create(BASE_URL, { + importer: (filePath) => { + if (filePath.startsWith('./') || filePath.startsWith('../')) { + return import(new URL(filePath, BASE_URL).href) + } + + return import(filePath) + }, + }) + + const app = ignitor.createApp('web') + await app.init() + await app.boot() + + await fs.create('.env', '') + await fs.createJson('tsconfig.json', {}) + await fs.create('start/env.ts', `export default Env.create(new URL('./'), {})`) + await fs.create('adonisrc.ts', `export default defineConfig({})`) + + const ace = await app.container.make('ace') + const command = await ace.create(Configure, ['../../index.js', '--providers=foo']) + await command.exec() + + command.assertFailed() + + await assert.fileNotExists('config/ally.ts') + await assert.fileEquals('.env', '') + await assert.fileEquals('start/env.ts', `export default Env.create(new URL('./'), {})`) + }) + + test('prompt when provider is pre-defined', async ({ fs, assert }) => { const ignitor = new IgnitorFactory() .withCoreProviders() .withCoreConfig() @@ -79,5 +225,5 @@ test.group('Configure', (group) => { await assert.fileContains('start/env.ts', 'GITHUB_CLIENT_SECRET: Env.schema.string()') await assert.fileContains('start/env.ts', 'LINKEDIN_CLIENT_ID: Env.schema.string()') await assert.fileContains('start/env.ts', 'LINKEDIN_CLIENT_SECRET: Env.schema.string()') - }).timeout(1000 * 60) + }) }) From f66354d2c44a02bb6ef29eb99d93f5abf8e5cbcd Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sun, 24 Dec 2023 15:42:23 +0530 Subject: [PATCH 32/37] docs(README): update link to docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 335e010..b828a3d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Social authentication provider for AdonisJS. Supports *Github*, Google, Twitter, Facebook, Discord, Spotify, and LinkedIn. ## Official Documentation -The documentation is available on the [AdonisJS website](https://docs.adonisjs.com/guides/auth/social) +The documentation is available on the [AdonisJS website](https://docs.adonisjs.com/guides/social_auth) ## Contributing One of the primary goals of AdonisJS is to have a vibrant community of users and contributors who believes in the principles of the framework. From 5f081db7efbf50293028723b0ecd099043de8c77 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sun, 24 Dec 2023 15:43:23 +0530 Subject: [PATCH 33/37] docs: update description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b828a3d..2b979e6 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] ## Introduction -Social authentication provider for AdonisJS. Supports *Github*, Google, Twitter, Facebook, Discord, Spotify, and LinkedIn. +Social authentication provider for AdonisJS. Supports **Github**, **Google**, **Twitter**, **Facebook**, **Discord**, **Spotify**, and **LinkedIn**. ## Official Documentation The documentation is available on the [AdonisJS website](https://docs.adonisjs.com/guides/social_auth) From 74412a534d59839b6fe97caa6b0527eb24ea6330 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sun, 24 Dec 2023 15:54:17 +0530 Subject: [PATCH 34/37] chore(release): 5.0.0-8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4649312..2e90562 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/ally", - "version": "5.0.0-7", + "version": "5.0.0-8", "description": "Social authentication provider for AdonisJS", "type": "module", "main": "build/index.js", From 7bffd15e40c014a5f93dafae1be93a21f28f2aa0 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 9 Jan 2024 14:34:13 +0530 Subject: [PATCH 35/37] chore: update dependencies --- package.json | 22 +++++++++++----------- tests/ally_provider.spec.ts | 7 +------ 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 2e90562..6a30ec8 100644 --- a/package.json +++ b/package.json @@ -46,20 +46,20 @@ "author": "adonisjs,virk", "license": "MIT", "devDependencies": { - "@adonisjs/assembler": "^7.0.0-0", - "@adonisjs/core": "^6.1.5-34", - "@adonisjs/eslint-config": "^1.2.0", - "@adonisjs/prettier-config": "^1.2.0", - "@adonisjs/tsconfig": "^1.2.0", - "@commitlint/cli": "^18.4.3", - "@commitlint/config-conventional": "^18.4.3", + "@adonisjs/assembler": "^7.0.0", + "@adonisjs/core": "^6.2.0", + "@adonisjs/eslint-config": "^1.2.1", + "@adonisjs/prettier-config": "^1.2.1", + "@adonisjs/tsconfig": "^1.2.1", + "@commitlint/cli": "^18.4.4", + "@commitlint/config-conventional": "^18.4.4", "@japa/assert": "^2.1.0", "@japa/expect-type": "^2.0.1", "@japa/file-system": "^2.1.1", "@japa/runner": "^3.1.1", - "@swc/core": "^1.3.101", - "@types/node": "^20.10.5", - "c8": "^8.0.1", + "@swc/core": "^1.3.102", + "@types/node": "^20.10.7", + "c8": "^9.0.0", "copyfiles": "^2.4.1", "del-cli": "^5.1.0", "dotenv": "^16.3.1", @@ -78,7 +78,7 @@ "@poppinss/utils": "^6.7.0" }, "peerDependencies": { - "@adonisjs/core": "^6.1.5-34" + "@adonisjs/core": "^6.2.0" }, "repository": { "type": "git", diff --git a/tests/ally_provider.spec.ts b/tests/ally_provider.spec.ts index a0fe299..d61249b 100644 --- a/tests/ally_provider.spec.ts +++ b/tests/ally_provider.spec.ts @@ -15,9 +15,6 @@ import { AllyManager } from '../src/ally_manager.js' import { defineConfig, services } from '../src/define_config.js' const BASE_URL = new URL('./tmp/', import.meta.url) -const IMPORTER = (filePath: string) => { - return import(filePath) -} test.group('Ally provider', () => { test('define HttpContext.ally property', async ({ assert }) => { @@ -40,9 +37,7 @@ test.group('Ally provider', () => { }), }, }) - .create(BASE_URL, { - importer: IMPORTER, - }) + .create(BASE_URL) const app = ignitor.createApp('web') await app.init() From 1205160cb9689f51414addc2ac819a9ca3155fd0 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 9 Jan 2024 14:37:16 +0530 Subject: [PATCH 36/37] refactor: export stubsRoot --- index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/index.ts b/index.ts index 269ce30..af664d4 100644 --- a/index.ts +++ b/index.ts @@ -11,6 +11,7 @@ export { HttpClient as ApiRequest } from '@poppinss/oauth-client' export * as errors from './src/errors.js' export { configure } from './configure.js' +export { stubsRoot } from './stubs/main.js' export { AllyManager } from './src/ally_manager.js' export { defineConfig, services } from './src/define_config.js' From 13dd7118be489fdc4120c6cf54c69bf0cef86ce8 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 9 Jan 2024 15:19:38 +0530 Subject: [PATCH 37/37] chore: bundle types.ts file via tsup as well --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 6a30ec8..227b625 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,8 @@ }, "tsup": { "entry": [ - "index.ts", + "./index.ts", + "./src/types.ts", "./providers/ally_provider.ts" ], "outDir": "./build",