mirror of
https://github.com/bitinflow/nuxt-oauth.git
synced 2026-03-13 05:35: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