17 Commits

Author SHA1 Message Date
René Preuß
761960dc8a Bump version 2024-10-04 10:08:11 +02:00
René Preuß
6c2d6630ac Update CHANGELOG.md 2024-10-04 10:06:28 +02:00
René Preuß
1c05acbcdd Add cookie options
Bump axios version
Change default urls to example.com
Update documentation
2024-10-04 10:00:48 +02:00
René Preuß
ef07ddb80e Change ci command 2023-09-12 11:24:50 +02:00
René Preuß
0b3479ac01 Change ci command 2023-09-12 11:22:43 +02:00
René Preuß
b80604529c Change to yarn 2023-09-12 11:17:10 +02:00
René Preuß
3ed1fd729a Merge pull request #2 from bitinflow/ghostzero-patch-1
Update package.yml
2023-09-12 11:13:15 +02:00
René Preuß
197a1a5c8d Update package.yml 2023-09-12 11:13:06 +02:00
René Preuß
df53718392 Change node version 2023-09-12 11:11:03 +02:00
René Preuß
ce2ced4ffa Bump version / add ci 2023-09-12 11:03:56 +02:00
René Preuß
7384a4c5ce Allow external urls for oauth redirects 2023-09-12 10:54:18 +02:00
René Preuß
eff160b3c5 chore(release): v2.0.2 2023-06-22 21:45:59 +02:00
René Preuß
464e72535b Merge pull request #1 from bitinflow/envoyr-patch-1
Allow directory slash for login callback url
2023-06-22 21:43:39 +02:00
a77e689b38 Update plugin.ts 2023-06-22 21:32:28 +02:00
René Preuß
3ce7d64d50 Update CHANGELOG.md 2023-04-08 18:37:50 +02:00
René Preuß
6864194251 chore(release): v2.0.1 2023-04-08 18:33:28 +02:00
René Preuß
fc4abb27d5 Fix documentation 2023-04-08 18:33:00 +02:00
11 changed files with 5773 additions and 10002 deletions

22
.github/workflows/package.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Node.js Package
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: '18.x'
registry-url: 'https://registry.npmjs.org'
scope: '@bitinflow'
- run: yarn install
- run: yarn ci
- run: yarn publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -1,18 +1,41 @@
# Changelog
## v2.0.4
Add `cookies` option in `ModuleOptions` interface
## v2.0.3
Add ", { external: true }" in navigateTo
## v2.0.2
Fix for `auth` middleware
## v2.0.1
## v1.0.6
Typo fixes in the GitHub/NPM repo
## v1.0.5
## v2.0.0
Support for Authorization Code Grant with PKCE
## v1.0.5 - v1.0.6
Fix for CookieRef when using watch(...)
## v1.0.4
Minor fixes
## v1.0.3
Minor fixes
## v1.0.2
Minor fixes
## v1.0.0
Initial Release

View File

@@ -54,7 +54,7 @@ export default defineNuxtConfig({
'/whatever/**': {ssr: false}
},
// using code response type (default)
// example 1: using code response type (default)
oauth: {
endpoints: {
authorization: 'https://example.com/oauth/authorize',
@@ -66,7 +66,7 @@ export default defineNuxtConfig({
scope: ['user:read']
},
// using token response type (not recommended)
// example 2: using token response type (not recommended)
oauth: {
endpoints: {
authorization: 'https://example.com/oauth/authorize',
@@ -82,10 +82,73 @@ export default defineNuxtConfig({
This will be your callback url (host is determined by `window.location.origin`):
- Callback: `http://localhost:3000/auth/login`
- Callback: `http://localhost:3000/login`
That's it! You can now use @bitinflow/nuxt-oauth in your Nuxt app ✨
## Module Options
The module provides a set of customizable options to configure OAuth-based authentication for your application. Below is a detailed description of each option and its default values:
### `redirect`
This option defines the URLs for redirection during the authentication process.
- `login` (`string`): The URL to redirect to when a user needs to log in. Default: `/login`.
- `logout` (`string`): The URL to redirect to after logging out. Default: `/`.
- `callback` (`string`): The URL to handle the OAuth callback. Default: `/login`.
- `home` (`string`): The URL to redirect to after successful authentication. Default: `/`.
### `endpoints`
Configures the OAuth server endpoints for authorization, token exchange, and user information retrieval.
- `authorization` (`string`): The OAuth authorization endpoint. Default: `https://example.com/oauth/authorize`.
- `token` (`string`): The OAuth token endpoint. Default: `https://example.com/oauth/token`.
- `userInfo` (`string`): The endpoint to retrieve user information. Default: `https://example.com/api/users/me`.
- `logout` (`string | null`): The endpoint for logging out from the OAuth provider. Default: `null`.
### `refreshToken`
Manages the refresh token settings.
- `maxAge` (`number`): The maximum age (in seconds) for storing the refresh token in cookies. Default: `60 * 60 * 24 * 30` (30 days).
### `cookies`
Configures cookie settings for storing OAuth tokens and related data.
- `prefix` (`string`): A prefix for all cookie names. Default: none.
- `names`: Specific names for different OAuth-related cookies.
- `oauth_user`: The cookie name for storing the OAuth user. Default: `oauth_user`.
- `oauth_state`: The cookie name for storing the OAuth state. Default: `oauth_state`.
- `oauth_code_verifier`: The cookie name for storing the OAuth code verifier. Default: `oauth_code_verifier`.
- `oauth_access_token`: The cookie name for storing the access token. Default: `oauth_access_token`.
- `oauth_refresh_token`: The cookie name for storing the refresh token. Default: `oauth_refresh_token`.
- `options`: Additional settings for cookie behavior.
- `path` (`string`): The cookie path. Default: none.
- `maxAge` (`number`): The cookie's maximum age (in seconds). Default: none.
- `secure` (`boolean`): Whether the cookie should only be sent over HTTPS. Default: none.
- `sameSite` (`string`): Sets the `SameSite` cookie attribute (`lax`, `strict`, or `none`). Default: none.
- `domain` (`string`): Specifies the cookie's domain. Default: none.
- `httpOnly` (`boolean`): Indicates if the cookie is inaccessible to JavaScript. Default: none.
### `clientId`
- (`string`): The client ID used for OAuth authentication. Default: `please-set-client-id`.
### `responseType`
- (`'token' | 'code'`): The type of OAuth response, either token-based or code-based flow. Default: `code`.
### `prompt`
- (`'' | 'none' | 'login' | 'consent'`): The prompt parameter to control the OAuth flow. Default: `''`.
### `scope`
- (`string[]`): The OAuth scopes requested during authentication. Default: `[]` (empty array).
## Development
```bash

9972
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@bitinflow/nuxt-oauth",
"version": "2.0.1",
"version": "2.0.4",
"description": "Nuxt 3 OAuth Module",
"license": "MIT",
"type": "module",
@@ -21,7 +21,9 @@
"dev": "nuxi dev playground",
"dev:build": "nuxi build playground",
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground",
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish --access public && git push --follow-tags",
"ci": "npm run lint && npm run dev:prepare && npm run test && npm run prepack",
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && git push --follow-tags",
"push": "npm publish --access public",
"lint": "eslint .",
"test": "vitest run",
"test:watch": "vitest watch"
@@ -35,7 +37,7 @@
"@nuxt/module-builder": "^0.2.1",
"@nuxt/schema": "^3.2.2",
"@nuxt/test-utils": "^3.2.2",
"axios": "^1.3.5",
"axios": "^1.6.0",
"changelogen": "^0.4.1",
"eslint": "^8.34.0",
"nuxt": "^3.2.2",

View File

@@ -7,6 +7,11 @@ export default defineNuxtConfig({
redirect: {
home: '/home'
},
cookies: {
options: {
domain: '*.own3d.pro'
}
},
clientId: '98e1cb74-125a-4d60-b686-02c2f0c87521',
scope: ['user:read']
},

View File

@@ -18,6 +18,24 @@ export interface ModuleOptions {
refreshToken?: {
maxAge: number,
}
cookies?: {
prefix?: string,
names?: {
oauth_user?: 'oauth_user'
oauth_state?: 'oauth_state'
oauth_code_verifier?: 'oauth_code_verifier'
oauth_access_token?: 'oauth_access_token'
oauth_refresh_token?: 'oauth_refresh_token'
}
options?: {
path?: string,
maxAge?: number,
secure?: boolean,
sameSite?: string,
domain?: string,
httpOnly?: boolean
}
}
clientId?: string,
responseType?: 'token' | 'code',
prompt?: '' | 'none' | 'login' | 'consent',
@@ -32,9 +50,9 @@ const defaults: ModuleOptions = {
home: '/'
},
endpoints: {
authorization: 'https://accounts.bitinflow.com/oauth/authorize',
token: 'https://accounts.bitinflow.com/oauth/token',
userInfo: 'https://accounts.bitinflow.com/api/v3/user',
authorization: 'https://example.com/oauth/authorize',
token: 'https://example.com/oauth/token',
userInfo: 'https://example.com/api/users/me',
logout: null,
},
refreshToken: {

View File

@@ -1,6 +1,6 @@
import {CookieRef, navigateTo, useCookie, useRuntimeConfig} from "#app";
import {CookieRef, navigateTo, useRuntimeConfig} from "#app";
import {ModuleOptions} from "../../module";
import {generateRandomString, getChallengeFromVerifier} from "../support";
import { generateRandomString, getChallengeFromVerifier, useCookie } from '../support'
declare interface ComposableOptions {
fetchUserOnInitialization: boolean
@@ -14,9 +14,9 @@ export default async (options: ComposableOptions = {
fetchUserOnInitialization: false
}) => {
const authConfig = useRuntimeConfig().public.oauth as ModuleOptions;
if (user == null) user = useCookie('oauth_user');
if (accessToken == null) accessToken = useCookie('oauth_access_token');
if (refreshToken == null) refreshToken = useCookie('oauth_refresh_token');
if (user == null) user = useCookie('oauth_user', authConfig);
if (accessToken == null) accessToken = useCookie('oauth_access_token', authConfig);
if (refreshToken == null) refreshToken = useCookie('oauth_refresh_token', authConfig);
const fetchUser = async (): Promise<void> => {
try {
@@ -36,7 +36,7 @@ export default async (options: ComposableOptions = {
}
const signIn = async (): Promise<void> => {
const state = useCookie<string>('oauth_state');
const state = useCookie<string>('oauth_state', authConfig);
state.value = generateRandomString();
// create oauth authorization url
@@ -50,7 +50,7 @@ export default async (options: ComposableOptions = {
})
if (authConfig.responseType === 'code') {
const codeVerifier = useCookie<string>('oauth_code_verifier');
const codeVerifier = useCookie<string>('oauth_code_verifier', authConfig);
codeVerifier.value = generateRandomString();
params.set('code_challenge', await getChallengeFromVerifier(codeVerifier.value))
@@ -74,7 +74,7 @@ export default async (options: ComposableOptions = {
window.location.href = `${authConfig.endpoints.logout}?${params.toString()}`
}
return navigateTo(authConfig.redirect.logout)
return navigateTo(authConfig.redirect.logout, { external: true })
}
const setBearerToken = async (token: string, tokenType: string, expires: number): Promise<void> => {

View File

@@ -1,7 +1,8 @@
import {addRouteMiddleware, defineNuxtPlugin, navigateTo, useCookie} from '#app'
import {addRouteMiddleware, defineNuxtPlugin, navigateTo} from '#app'
import useAuth from "./composables/useAuth"
import {RouteLocationNormalized} from "vue-router";
import {ModuleOptions} from "../module";
import {useCookie} from "./support";
interface AccessToken {
access_token: string,
@@ -25,7 +26,7 @@ export default defineNuxtPlugin(() => {
const expires = hashParams.get('expires_in') as string;
await setBearerToken(token, tokenType, parseInt(expires));
return navigateTo(authConfig.redirect.home)
return navigateTo(authConfig.redirect.home, { external: true })
}
}
@@ -39,12 +40,13 @@ export default defineNuxtPlugin(() => {
if (to.query['code']) {
const code = to.query['code'] as string;
const stateFromRequest = to.query['state'] as string;
const stateFromCookie = useCookie<string>('oauth_state');
const codeVerifier = useCookie<string>('oauth_code_verifier');
console.log('options', authConfig.cookies?.options)
const stateFromCookie = useCookie<string>('oauth_state', authConfig);
const codeVerifier = useCookie<string>('oauth_code_verifier', authConfig);
if (stateFromRequest !== stateFromCookie.value) {
console.warn('State mismatch', stateFromRequest, stateFromCookie.value)
return navigateTo(authConfig.redirect.login)
return navigateTo(authConfig.redirect.login, { external: true })
}
const formData = new FormData();
@@ -61,23 +63,23 @@ export default defineNuxtPlugin(() => {
if (!response.ok) {
console.warn('Failed to fetch token', response)
return navigateTo(authConfig.redirect.login)
return navigateTo(authConfig.redirect.login, { external: true })
}
const data: AccessToken = await response.json();
await setBearerToken(data.access_token, data.token_type, data.expires_in)
await setRefreshToken(data.refresh_token, data.token_type, authConfig.refreshToken.maxAge)
return navigateTo(authConfig.redirect.home)
return navigateTo(authConfig.redirect.home, { external: true })
}
}
addRouteMiddleware('auth', async (to) => {
const {user, authConfig, setBearerToken, setRefreshToken} = await useAuth()
if (to.path === authConfig.redirect.callback) {
if (to.path === authConfig.redirect.callback || to.path === authConfig.redirect.callback + '/') {
const queryParams = new URLSearchParams(to.query.toString());
if (queryParams.has('error')) {
return navigateTo(authConfig.redirect.login)
return navigateTo(authConfig.redirect.login, { external: true })
}
if (authConfig.responseType === 'token') {
@@ -92,7 +94,7 @@ export default defineNuxtPlugin(() => {
}
if (user.value === undefined) {
return navigateTo(authConfig.redirect.login)
return navigateTo(authConfig.redirect.login, { external: true })
}
})
@@ -100,7 +102,7 @@ export default defineNuxtPlugin(() => {
const {user, authConfig} = await useAuth()
if (user.value !== undefined) {
return navigateTo(authConfig.redirect.home)
return navigateTo(authConfig.redirect.home, { external: true })
}
})
})

View File

@@ -2,6 +2,9 @@
* Source: https://docs.cotter.app/sdk-reference/api-for-other-mobile-apps/api-for-mobile-apps
*/
import { ModuleOptions } from '../module'
import {useCookie as _useCookie, CookieRef} from "#app";
function dec2hex(dec: any) {
return ('0' + dec.toString(16)).substr(-2)
}
@@ -34,3 +37,10 @@ function base64urlencode(a: any) {
export async function getChallengeFromVerifier(v: any) {
return base64urlencode(await sha256(v));
}
export function useCookie<T = string | null | undefined>(name: string, options: ModuleOptions): CookieRef<T> {
const cookieName = options.cookies?.names?.[name] || name;
const cookiePrefix = options.cookies?.prefix || '';
const cookieOptions = options.cookies?.options || {};
return _useCookie(cookiePrefix + cookieName, cookieOptions)
}

5598
yarn.lock Normal file

File diff suppressed because it is too large Load Diff