mirror of
https://github.com/bitinflow/nuxt-oauth.git
synced 2026-03-13 13:45:59 +00:00
first commit
This commit is contained in:
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
dist
|
||||||
|
node_modules
|
||||||
4
.eslintrc
Normal file
4
.eslintrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": ["@nuxt/eslint-config"]
|
||||||
|
}
|
||||||
56
.gitignore
vendored
Normal file
56
.gitignore
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log*
|
||||||
|
|
||||||
|
# Temp directories
|
||||||
|
.temp
|
||||||
|
.tmp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Yarn
|
||||||
|
**/.yarn/cache
|
||||||
|
**/.yarn/*state*
|
||||||
|
|
||||||
|
# Generated dirs
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Nuxt
|
||||||
|
.nuxt
|
||||||
|
.output
|
||||||
|
.vercel_build_output
|
||||||
|
.build-*
|
||||||
|
.env
|
||||||
|
.netlify
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
reports
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Intellij idea
|
||||||
|
*.iml
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# OSX
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
2
.nuxtrc
Normal file
2
.nuxtrc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
imports.autoImport=false
|
||||||
|
typescript.includeWorkspace=true
|
||||||
80
README.md
Normal file
80
README.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# @bitinflow/nuxt-oauth
|
||||||
|
|
||||||
|
[![npm version][npm-version-src]][npm-version-href]
|
||||||
|
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
||||||
|
[![License][license-src]][license-href]
|
||||||
|
|
||||||
|
> My new Nuxt module
|
||||||
|
|
||||||
|
- [✨ Release Notes](/CHANGELOG.md)
|
||||||
|
<!-- - [📖 Documentation](https://example.com) -->
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
<!-- Highlight some of the features your module provide here -->
|
||||||
|
- ⛰ Foo
|
||||||
|
- 🚠 Bar
|
||||||
|
- 🌲 Baz
|
||||||
|
|
||||||
|
## Quick Setup
|
||||||
|
|
||||||
|
1. Add `my-module` dependency to your project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using pnpm
|
||||||
|
pnpm add -D my-module
|
||||||
|
|
||||||
|
# Using yarn
|
||||||
|
yarn add --dev my-module
|
||||||
|
|
||||||
|
# Using npm
|
||||||
|
npm install --save-dev my-module
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add `my-module` to the `modules` section of `nuxt.config.ts`
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
modules: [
|
||||||
|
'my-module'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! You can now use My Module in your Nuxt app ✨
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Generate type stubs
|
||||||
|
npm run dev:prepare
|
||||||
|
|
||||||
|
# Develop with the playground
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Build the playground
|
||||||
|
npm run dev:build
|
||||||
|
|
||||||
|
# Run ESLint
|
||||||
|
npm run lint
|
||||||
|
|
||||||
|
# Run Vitest
|
||||||
|
npm run test
|
||||||
|
npm run test:watch
|
||||||
|
|
||||||
|
# Release new version
|
||||||
|
npm run release
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- Badges -->
|
||||||
|
[npm-version-src]: https://img.shields.io/npm/v/my-module/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
|
||||||
|
[npm-version-href]: https://npmjs.com/package/my-module
|
||||||
|
|
||||||
|
[npm-downloads-src]: https://img.shields.io/npm/dm/my-module.svg?style=flat&colorA=18181B&colorB=28CF8D
|
||||||
|
[npm-downloads-href]: https://npmjs.com/package/my-module
|
||||||
|
|
||||||
|
[license-src]: https://img.shields.io/npm/l/my-module.svg?style=flat&colorA=18181B&colorB=28CF8D
|
||||||
|
[license-href]: https://npmjs.com/package/my-module
|
||||||
9892
package-lock.json
generated
Normal file
9892
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
package.json
Normal file
43
package.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "@bitinflow/nuxt-oauth",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Nuxt 3 OAuth Module",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/types.d.ts",
|
||||||
|
"import": "./dist/module.mjs",
|
||||||
|
"require": "./dist/module.cjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"main": "./dist/module.cjs",
|
||||||
|
"types": "./dist/types.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"prepack": "nuxt-module-build",
|
||||||
|
"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 && git push --follow-tags",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nuxt/kit": "^3.2.2",
|
||||||
|
"defu": "^6.1.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nuxt/eslint-config": "^0.1.1",
|
||||||
|
"@nuxt/module-builder": "^0.2.1",
|
||||||
|
"@nuxt/schema": "^3.2.2",
|
||||||
|
"@nuxt/test-utils": "^3.2.2",
|
||||||
|
"changelogen": "^0.4.1",
|
||||||
|
"eslint": "^8.34.0",
|
||||||
|
"nuxt": "^3.2.2",
|
||||||
|
"vitest": "^0.28.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
playground/nuxt.config.ts
Normal file
18
playground/nuxt.config.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export default defineNuxtConfig({
|
||||||
|
modules: ['../src/module'],
|
||||||
|
oauth: {
|
||||||
|
redirect: {
|
||||||
|
login: '/login/', // sandbox appends / at the end of url
|
||||||
|
logout: '/',
|
||||||
|
callback: '/login/', // sandbox appends / at the end of url
|
||||||
|
home: '/home'
|
||||||
|
},
|
||||||
|
endpoints: {
|
||||||
|
authorization: 'https://api.sandbox.own3d.pro/v1/oauth/authorization',
|
||||||
|
userInfo: `https://id.stream.tv/api/users/@me`,
|
||||||
|
logout: 'https://id.stream.tv/oauth/token'
|
||||||
|
},
|
||||||
|
clientId: '90a951d1-ea50-4fda-8c4d-275b81f7d219',
|
||||||
|
scope: ['user:read', 'connections']
|
||||||
|
},
|
||||||
|
})
|
||||||
4
playground/package.json
Normal file
4
playground/package.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"name": "my-module-playground"
|
||||||
|
}
|
||||||
11
playground/pages/guest.vue
Normal file
11
playground/pages/guest.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Guest only page
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
definePageMeta({
|
||||||
|
middleware: ["guest"]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
20
playground/pages/home.vue
Normal file
20
playground/pages/home.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {useAuth} from "#imports";
|
||||||
|
|
||||||
|
const {user, signOut} = await useAuth();
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
middleware: ["auth"]
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="user">
|
||||||
|
Hello {{ user.name }}
|
||||||
|
|
||||||
|
<button @click="signOut">
|
||||||
|
Sign Out
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
11
playground/pages/index.vue
Normal file
11
playground/pages/index.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Index page
|
||||||
|
<nuxt-link to="/login">
|
||||||
|
Login
|
||||||
|
</nuxt-link>
|
||||||
|
<nuxt-link to="/guest">
|
||||||
|
Guest
|
||||||
|
</nuxt-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
18
playground/pages/login.vue
Normal file
18
playground/pages/login.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {definePageMeta, useAuth} from "#imports";
|
||||||
|
|
||||||
|
const {signIn} = await useAuth();
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
middleware: ["auth"]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Login</h1>
|
||||||
|
<button @click="signIn">
|
||||||
|
Sign In
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
65
src/module.ts
Normal file
65
src/module.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import {addImportsDir, addPlugin, createResolver, defineNuxtModule} from '@nuxt/kit'
|
||||||
|
import defu from "defu";
|
||||||
|
|
||||||
|
// Module options TypeScript interface definition
|
||||||
|
export interface ModuleOptions {
|
||||||
|
redirect: {
|
||||||
|
login: string,
|
||||||
|
logout: string,
|
||||||
|
callback: string,
|
||||||
|
home: string
|
||||||
|
},
|
||||||
|
endpoints: {
|
||||||
|
authorization: string,
|
||||||
|
userInfo: string,
|
||||||
|
logout: string
|
||||||
|
},
|
||||||
|
clientId: string,
|
||||||
|
scope: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaults: ModuleOptions = {
|
||||||
|
redirect: {
|
||||||
|
login: '/login',
|
||||||
|
logout: '/',
|
||||||
|
callback: '/login',
|
||||||
|
home: '/'
|
||||||
|
},
|
||||||
|
endpoints: {
|
||||||
|
authorization: 'https://accounts.bitinflow.com/oauth/authorize',
|
||||||
|
userInfo: `https://accounts.bitinflow.com/api/v3/user`,
|
||||||
|
logout: 'https://accounts.bitinflow.com/logout'
|
||||||
|
},
|
||||||
|
clientId: 'please-set-client-id',
|
||||||
|
scope: ['user:read']
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineNuxtModule<ModuleOptions>({
|
||||||
|
meta: {
|
||||||
|
name: '@bitinflow/nuxt-oauth',
|
||||||
|
configKey: 'oauth'
|
||||||
|
},
|
||||||
|
defaults,
|
||||||
|
setup (moduleOptions, nuxt) {
|
||||||
|
const resolver = createResolver(import.meta.url)
|
||||||
|
|
||||||
|
const options = defu(moduleOptions, {
|
||||||
|
...defaults
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set up runtime configuration
|
||||||
|
nuxt.options.runtimeConfig = nuxt.options.runtimeConfig || { public: {} }
|
||||||
|
nuxt.options.runtimeConfig.oauth = defu(nuxt.options.runtimeConfig.oauth, {
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
nuxt.options.runtimeConfig.public.oauth = defu(nuxt.options.runtimeConfig.public.oauth, {
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
|
||||||
|
// Do not add the extension since the `.ts` will be transpiled to `.mjs` after `npm run prepack`
|
||||||
|
addPlugin(resolver.resolve('./runtime/plugin'))
|
||||||
|
|
||||||
|
const composables = resolver.resolve('./runtime/composables')
|
||||||
|
addImportsDir(composables)
|
||||||
|
}
|
||||||
|
})
|
||||||
68
src/runtime/composables/useAuth.ts
Normal file
68
src/runtime/composables/useAuth.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import {CookieRef, navigateTo, useCookie, useRuntimeConfig} from "#app";
|
||||||
|
import {ModuleOptions} from "../../module";
|
||||||
|
|
||||||
|
declare interface ComposableOptions {
|
||||||
|
fetchUserOnInitialization: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async (options: ComposableOptions = {
|
||||||
|
fetchUserOnInitialization: false
|
||||||
|
}) => {
|
||||||
|
const user: CookieRef<any> = useCookie('oauth_user')
|
||||||
|
const accessToken: CookieRef<any> = useCookie('oauth_access_token')
|
||||||
|
const authConfig = useRuntimeConfig().public.oauth as ModuleOptions;
|
||||||
|
|
||||||
|
const fetchUser = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(authConfig.endpoints.userInfo, {
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `${accessToken.value.tokenType} ${accessToken.value.token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
user.value = response.ok
|
||||||
|
? await response.json()
|
||||||
|
: null;
|
||||||
|
} catch (e) {
|
||||||
|
user.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const signIn = async () => {
|
||||||
|
// create oauth authorization url
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
client_id: authConfig.clientId,
|
||||||
|
redirect_uri: window.location.origin + authConfig.redirect.callback,
|
||||||
|
response_type: 'token',
|
||||||
|
scope: authConfig.scope.join(' ')
|
||||||
|
})
|
||||||
|
|
||||||
|
window.location.href = `${authConfig.endpoints.authorization}?${params.toString()}`
|
||||||
|
};
|
||||||
|
|
||||||
|
const signOut = async () => {
|
||||||
|
accessToken.value = null;
|
||||||
|
user.value = null;
|
||||||
|
|
||||||
|
return navigateTo('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
const setBearer = async (token: string, tokenType: string, expires: number) => {
|
||||||
|
accessToken.value = {token, tokenType, expiresAt: Date.now() + expires * 1000};
|
||||||
|
await fetchUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the user if the option is set to true
|
||||||
|
if (options.fetchUserOnInitialization) {
|
||||||
|
await fetchUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
signIn,
|
||||||
|
signOut,
|
||||||
|
setBearer,
|
||||||
|
authConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/runtime/plugin.ts
Normal file
35
src/runtime/plugin.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import {addRouteMiddleware, defineNuxtPlugin, navigateTo} from '#app'
|
||||||
|
import useAuth from "./composables/useAuth"
|
||||||
|
|
||||||
|
export default defineNuxtPlugin(() => {
|
||||||
|
addRouteMiddleware('auth', async (to) => {
|
||||||
|
const {user, authConfig, setBearer} = await useAuth()
|
||||||
|
|
||||||
|
if (to.path === authConfig.redirect.callback) {
|
||||||
|
const params = new URLSearchParams(to.hash.substring(1))
|
||||||
|
|
||||||
|
if (params.has('access_token')) {
|
||||||
|
const token = params.get('access_token') as string;
|
||||||
|
const tokenType = params.get('token_type') as string;
|
||||||
|
const expires = params.get('expires_in') as string;
|
||||||
|
|
||||||
|
await setBearer(token, tokenType, parseInt(expires));
|
||||||
|
return navigateTo(authConfig.redirect.home)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.value === undefined) {
|
||||||
|
return navigateTo(authConfig.redirect.login)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
addRouteMiddleware('guest', async () => {
|
||||||
|
const {user, authConfig} = await useAuth()
|
||||||
|
|
||||||
|
if (user.value !== undefined) {
|
||||||
|
return navigateTo(authConfig.redirect.home)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
15
test/basic.test.ts
Normal file
15
test/basic.test.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { setup, $fetch } from '@nuxt/test-utils'
|
||||||
|
|
||||||
|
describe('ssr', async () => {
|
||||||
|
await setup({
|
||||||
|
rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url)),
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the index page', async () => {
|
||||||
|
// Get response to a server-rendered page with `$fetch`.
|
||||||
|
const html = await $fetch('/')
|
||||||
|
expect(html).toContain('<div>basic</div>')
|
||||||
|
})
|
||||||
|
})
|
||||||
6
test/fixtures/basic/app.vue
vendored
Normal file
6
test/fixtures/basic/app.vue
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<template>
|
||||||
|
<div>basic</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
</script>
|
||||||
7
test/fixtures/basic/nuxt.config.ts
vendored
Normal file
7
test/fixtures/basic/nuxt.config.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import MyModule from '../../../src/module'
|
||||||
|
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
modules: [
|
||||||
|
MyModule
|
||||||
|
]
|
||||||
|
})
|
||||||
5
test/fixtures/basic/package.json
vendored
Normal file
5
test/fixtures/basic/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"name": "basic",
|
||||||
|
"type": "module"
|
||||||
|
}
|
||||||
3
tsconfig.json
Normal file
3
tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "./playground/.nuxt/tsconfig.json"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user