├── .eslintrc.json ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc ├── LICENSE ├── README.md ├── env.d.ts ├── example ├── App.vue ├── assets │ └── main.css ├── main.ts ├── router.ts └── views │ ├── Dashboard.vue │ ├── Login.vue │ └── ResetPassword.vue ├── favicon.ico ├── index.html ├── package-lock.json ├── package.json ├── src ├── components │ ├── AuthorizerBasicAuthLogin.vue │ ├── AuthorizerForgotPassword.vue │ ├── AuthorizerMagicLinkLogin.vue │ ├── AuthorizerProvider.vue │ ├── AuthorizerResetPassword.vue │ ├── AuthorizerRoot.vue │ ├── AuthorizerSignup.vue │ ├── AuthorizerSocialLogin.vue │ ├── AuthorizerVerifyOtp.vue │ ├── Message.vue │ └── PasswordStrengthIndicator.vue ├── constants │ └── index.ts ├── icons │ ├── Apple.vue │ ├── Close.vue │ ├── Discord.vue │ ├── Facebook.vue │ ├── Github.vue │ ├── Google.vue │ ├── Linkedin.vue │ ├── Microsoft.vue │ ├── Roblox.vue │ └── Twitter.vue ├── index.ts ├── shims-vue.d.ts ├── state │ ├── globalConfig.ts │ └── globalContext.ts ├── styledComponents │ ├── StyledButton.vue │ ├── StyledFlex.vue │ ├── StyledFooter.vue │ ├── StyledLink.vue │ ├── StyledMessageWrapper.vue │ ├── StyledPasswordStrength.vue │ ├── StyledPasswordStrengthWrapper.vue │ ├── StyledSeparator.vue │ ├── StyledWrapper.vue │ └── index.ts ├── styles │ └── default.css ├── types │ └── index.ts └── utils │ ├── common.ts │ ├── url.ts │ └── window.ts ├── tsconfig.app.json ├── tsconfig.build-types.json ├── tsconfig.config.json ├── tsconfig.json └── vite.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "vue-eslint-parser", // Use the vue-eslint-parser to parse Vue.js files 3 | "parserOptions": { 4 | "parser": "@typescript-eslint/parser", // Use the @typescript-eslint/parser to parse TypeScript files 5 | "ecmaVersion": 2020, 6 | "sourceType": "module" 7 | }, 8 | "extends": [ 9 | "plugin:vue/recommended", // Use the recommended rules for Vue.js from the vue plugin 10 | "plugin:prettier-vue/recommended", // Use the recommended rules for Prettier with Vue.js from the prettier-vue plugin 11 | "plugin:@typescript-eslint/recommended" // Use the recommended rules for TypeScript from the @typescript-eslint plugin 12 | ], 13 | "settings": { 14 | "prettier-vue": { 15 | "SFCBlocks": { 16 | // Specify which blocks in Vue SFCs should be processed by Prettier 17 | "template": true, 18 | "script": true, 19 | "style": true, 20 | "customBlocks": { 21 | "docs": { "lang": "markdown" }, 22 | "config": { "lang": "json" }, 23 | "module": { "lang": "js" }, 24 | "comments": false 25 | } 26 | }, 27 | "usePrettierrc": true, // Use the Prettier configuration file in the project root 28 | "fileInfoOptions": { 29 | "ignorePath": ".testignore", // Ignore files listed in the .testignore file 30 | "withNodeModules": false // Ignore files in the node_modules directory 31 | } 32 | } 33 | }, 34 | "rules": { 35 | "prettier-vue/prettier": [ 36 | "error", 37 | { 38 | // Specify Prettier options for Vue.js files 39 | "useTabs": true, 40 | "printWidth": 100, 41 | "singleQuote": true, 42 | "trailingComma": "none" 43 | } 44 | ], 45 | "@typescript-eslint/no-unused-vars": [ 46 | "error", 47 | { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" } // Ignore unused variables that start with an underscore in TypeScript files 48 | ], 49 | "vue/multi-word-component-names": "off" // Disable the multi-word-component-names rule from the vue plugin 50 | }, 51 | "overrides": [ 52 | { 53 | "files": ["*.ts", "*.tsx"], 54 | "rules": { 55 | "no-undef": "off" // Disable the no-undef rule for TypeScript files 56 | } 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | .parcel-cache 7 | .yalc -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | git stash --keep-index -u 5 | npm run format 6 | git add . 7 | npm run lint 8 | git stash apply -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 authorizer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # authorizer-vue 2 | 3 | Authorizer Vue SDK allows you to implement authentication in your [Vue](https://vuejs.org/) application quickly. It also allows you to access the user profile. 4 | 5 | Here is a quick guide on getting started with `@authorizerdev/authorizer-vue` package. 6 | 7 | 8 | 9 | ## Code Sandbox Demo: https://codesandbox.io/s/authorizer-vue-example-700l1h 10 | 11 | ## Step 1 - Create Instance 12 | 13 | Get Authorizer URL by instantiating [Authorizer instance](/deployment) and configuring it with necessary [environment variables](/core/env). 14 | 15 | ## Step 2 - Install package 16 | 17 | Install `@authorizerdev/authorizer-vue` library 18 | 19 | ```sh 20 | npm i --save @authorizerdev/authorizer-vue 21 | OR 22 | yarn add @authorizerdev/authorizer-vue 23 | ``` 24 | 25 | ## Step 3 - Configure Provider and use Authorizer Components 26 | 27 | Authorizer comes with a [Provider](https://vuejs.org/api/composition-api-dependency-injection.html#provide) component that exposes a composable function to return a [reactive](https://vuejs.org/api/reactivity-core.html#reactive) context to it's children by using the `useAuthorizer` injection key, internally [toRefs](https://vuejs.org/api/reactivity-utilities.html#torefs) are used when returning the reactive state so that the consuming component(s) can destructure/spread the returned object without losing reactivity and each property could be watched to perform actions accordingly. 28 | 29 | ```vue 30 | 44 | 45 | 64 | ``` 65 | 66 | ```vue 67 | 74 | 75 | 116 | ``` 117 | 118 | ## Commands 119 | 120 | ### Local Development 121 | 122 | ### The recommended workflow is to run authorizer in one terminal: 123 | 124 | ```bash 125 | npm run dev # or yarn dev 126 | ``` 127 | 128 | This starts a local dev-server with a sandbox environment. 129 | 130 | ```bash 131 | npm run build # or yarn build 132 | ``` 133 | 134 | This uses Vite to build the project files to `/dist` and call `build:types` script. 135 | 136 | ```bash 137 | npm run build:types # or yarn build:types 138 | ``` 139 | 140 | This generates TypeScript declaration files for our .vue files (using `vue-tsc`). 141 | 142 | ```bash 143 | npm run typecheck # or yarn typecheck 144 | ``` 145 | 146 | This runs a typecheck against our Vue components to make sure there are no type errors (using `vue-tsc`). 147 | 148 | ## Configuration 149 | 150 | ### Typescript: 151 | 152 | - Root tsconfig: `tsconfig.json` contains a reference to all the others tsconfig files. 153 | - Components tsconfig: `tsconfig.app.json` will take care of compiling our Vue components inside `src/`. 154 | - Build-types tsconfig: `tsconfig.build-types.json` will take care of generating the proper types declaration files of our Vue components inside `src/`. 155 | - Tools tsconfig: `tsconfig.config.json` will take care of all our tooling configuration files (we only have vite at the moment). 156 | 157 | ### Vite: 158 | 159 | - Vite requires a configuration to compile and bundle `.vue` to `.js` files that can be consumed through an npm module. It uses [rollup.js](https://rollupjs.org/) under the hood, check out the comments in `vite.config.ts` file in the project root to learn more about the configuarition details. 160 | 161 | ### Eslint: 162 | 163 | - All required linting configurations are specified in the `.elsintrc.json` file in the project root, check the comments in each section to learn more about the configuarition details. 164 | 165 | ### Prettier: 166 | 167 | - We have the `"usePrettierrc"` option set to true in the `eslint` configuration file which tells the `prettier-vue` plugin to use the Prettier configuration file `.prettierrc` in the project root directory and override any default settings. 168 | 169 | ### Husky: 170 | 171 | - A pre-commit hook is set in `.husky/pre-commit` which formats the code and checks for any linting errors. 172 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /example/App.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/assets/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, system-ui, sans-serif; 3 | color: #374151; 4 | font-size: 14px; 5 | } 6 | 7 | *, 8 | *:before, 9 | *:after { 10 | box-sizing: inherit; 11 | } 12 | 13 | #app { 14 | display: flex; 15 | height: 100vh; 16 | flex-direction: column; 17 | justify-content: center; 18 | align-items: center; 19 | } 20 | -------------------------------------------------------------------------------- /example/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | 3 | import App from './App.vue'; 4 | import router from './router'; 5 | import '../src/styles/default.css'; 6 | import './assets/main.css'; 7 | 8 | createApp(App).use(router).mount('#app'); 9 | -------------------------------------------------------------------------------- /example/router.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router'; 2 | import Login from './views/Login.vue'; 3 | import ResetPassword from './views/ResetPassword.vue'; 4 | import Dashboard from './views/Dashboard.vue'; 5 | 6 | export default createRouter({ 7 | history: createWebHistory(), 8 | routes: [ 9 | { 10 | path: '/', 11 | component: Login 12 | }, 13 | { 14 | path: '/reset-password', 15 | component: ResetPassword 16 | }, 17 | { 18 | path: '/dashboard', 19 | component: Dashboard 20 | } 21 | ] 22 | }); 23 | -------------------------------------------------------------------------------- /example/views/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /example/views/Login.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /example/views/ResetPassword.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/authorizerdev/authorizer-vue/5cd64fc498a1b8c8bf605987160e179104b0ebd1/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Authorizer Demo App 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@authorizerdev/authorizer-vue", 3 | "version": "1.0.0", 4 | "description": "authorizer vue sdk", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/@authorizerdev/authorizer-vue.umd.js", 9 | "module": "dist/@authorizerdev/authorizer-vue.es.js", 10 | "types": "dist/types/index.d.ts", 11 | "scripts": { 12 | "dev": "vite", 13 | "build": "vite build && npm run build:types", 14 | "build:types": "vue-tsc --project tsconfig.build-types.json --declaration --emitDeclarationOnly --outDir dist/types ", 15 | "typecheck": "vue-tsc --project tsconfig.build-types.json --noEmit", 16 | "lint": "prettier --plugin-search-dir . --check . --ignore-path .gitignore && eslint . --ignore-path .gitignore", 17 | "format": "prettier --plugin-search-dir . --write . --ignore-path .gitignore", 18 | "prepare": "husky install" 19 | }, 20 | "keywords": [], 21 | "author": "Lakhan Samani", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/authorizerdev/authorizer-vue/issues" 25 | }, 26 | "homepage": "https://github.com/authorizerdev/authorizer-vue#readme", 27 | "devDependencies": { 28 | "@babel/types": "^7.21.4", 29 | "@tsconfig/node18": "^2.0.0", 30 | "@types/node": "^18.15.13", 31 | "@typescript-eslint/eslint-plugin": "^5.59.1", 32 | "@typescript-eslint/parser": "^5.59.1", 33 | "@vitejs/plugin-vue": "^4.1.0", 34 | "@vue/tsconfig": "^0.2.0", 35 | "eslint": "^8.39.0", 36 | "eslint-config-prettier": "^8.8.0", 37 | "eslint-plugin-prettier-vue": "^4.2.0", 38 | "eslint-plugin-vue": "^9.11.0", 39 | "husky": "^8.0.3", 40 | "prettier": "^2.8.8", 41 | "sass": "^1.62.0", 42 | "typescript": "^5.0.4", 43 | "vite": "^4.3.1", 44 | "vue": "^3.2.47", 45 | "vue-eslint-parser": "^9.1.1", 46 | "vue-router": "^4.1.6", 47 | "vue-tsc": "^1.4.2" 48 | }, 49 | "dependencies": { 50 | "@authorizerdev/authorizer-js": "^1.2.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/AuthorizerBasicAuthLogin.vue: -------------------------------------------------------------------------------- 1 | 71 | 72 | 214 | 215 | 218 | -------------------------------------------------------------------------------- /src/components/AuthorizerForgotPassword.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 138 | 139 | 142 | -------------------------------------------------------------------------------- /src/components/AuthorizerMagicLinkLogin.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 135 | 136 | 139 | -------------------------------------------------------------------------------- /src/components/AuthorizerProvider.vue: -------------------------------------------------------------------------------- 1 | 215 | -------------------------------------------------------------------------------- /src/components/AuthorizerResetPassword.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 175 | 176 | 179 | -------------------------------------------------------------------------------- /src/components/AuthorizerRoot.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 123 | -------------------------------------------------------------------------------- /src/components/AuthorizerSignup.vue: -------------------------------------------------------------------------------- 1 | 90 | 91 | 262 | 265 | -------------------------------------------------------------------------------- /src/components/AuthorizerSocialLogin.vue: -------------------------------------------------------------------------------- 1 | 146 | 147 | 224 | -------------------------------------------------------------------------------- /src/components/AuthorizerVerifyOtp.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 186 | 189 | -------------------------------------------------------------------------------- /src/components/Message.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 46 | -------------------------------------------------------------------------------- /src/components/PasswordStrengthIndicator.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 147 | 148 | 151 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export enum AuthorizerProviderActionType { 2 | SET_USER = 'SET_USER', 3 | SET_TOKEN = 'SET_TOKEN', 4 | SET_LOADING = 'SET_LOADING', 5 | SET_AUTH_DATA = 'SET_AUTH_DATA', 6 | SET_CONFIG = 'SET_CONFIG' 7 | } 8 | 9 | export enum Views { 10 | Login = 'Login', 11 | Signup = 'Signup', 12 | ForgotPassword = 'ForgotPassword' 13 | } 14 | 15 | export const ButtonAppearance: Record = { 16 | Primary: 'Primary', 17 | Default: 'Default' 18 | }; 19 | 20 | export const MessageType: Record = { 21 | Error: 'Error', 22 | Success: 'Success' 23 | }; 24 | 25 | // TODO use based on theme primary color 26 | export const passwordStrengthIndicatorOpacity: Record = { 27 | default: 0.15, 28 | weak: 0.4, 29 | good: 0.6, 30 | strong: 0.8, 31 | veryStrong: 1 32 | }; 33 | -------------------------------------------------------------------------------- /src/icons/Apple.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /src/icons/Close.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /src/icons/Discord.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | -------------------------------------------------------------------------------- /src/icons/Facebook.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 29 | -------------------------------------------------------------------------------- /src/icons/Github.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /src/icons/Google.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 38 | -------------------------------------------------------------------------------- /src/icons/Linkedin.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 28 | -------------------------------------------------------------------------------- /src/icons/Microsoft.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /src/icons/Roblox.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | -------------------------------------------------------------------------------- /src/icons/Twitter.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import AuthorizerProvider from './components/AuthorizerProvider.vue'; 2 | import AuthorizerSignup from './components/AuthorizerSignup.vue'; 3 | import AuthorizerBasicAuthLogin from './components/AuthorizerBasicAuthLogin.vue'; 4 | import AuthorizerMagicLinkLogin from './components/AuthorizerMagicLinkLogin.vue'; 5 | import AuthorizerForgotPassword from './components/AuthorizerForgotPassword.vue'; 6 | import AuthorizerSocialLogin from './components/AuthorizerSocialLogin.vue'; 7 | import AuthorizerResetPassword from './components/AuthorizerResetPassword.vue'; 8 | import AuthorizerVerifyOtp from './components/AuthorizerVerifyOtp.vue'; 9 | import AuthorizerRoot from './components/AuthorizerRoot.vue'; 10 | 11 | export { 12 | AuthorizerProvider, 13 | AuthorizerSignup, 14 | AuthorizerBasicAuthLogin, 15 | AuthorizerMagicLinkLogin, 16 | AuthorizerForgotPassword, 17 | AuthorizerSocialLogin, 18 | AuthorizerResetPassword, 19 | AuthorizerVerifyOtp, 20 | AuthorizerRoot 21 | }; 22 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // bridge between TypeScript and Vue, providing type definitions and declarations 3 | // that help TypeScript understand Vue's components and syntax 4 | declare module '*.vue' { 5 | import { DefineComponent } from 'vue'; 6 | const component: DefineComponent; 7 | export default component; 8 | } 9 | -------------------------------------------------------------------------------- /src/state/globalConfig.ts: -------------------------------------------------------------------------------- 1 | import type { AuthorizerConfig } from '../types'; 2 | import { reactive } from 'vue'; 3 | export default reactive({ 4 | authorizerURL: '', 5 | redirectURL: '', 6 | client_id: '', 7 | is_google_login_enabled: false, 8 | is_github_login_enabled: false, 9 | is_facebook_login_enabled: false, 10 | is_linkedin_login_enabled: false, 11 | is_apple_login_enabled: false, 12 | is_discord_login_enabled: false, 13 | is_roblox_login_enabled: false, 14 | is_email_verification_enabled: false, 15 | is_basic_authentication_enabled: false, 16 | is_magic_link_login_enabled: false, 17 | is_sign_up_enabled: false, 18 | is_strong_password_enabled: true, 19 | is_twitter_login_enabled: false, 20 | is_microsoft_login_enabled: false 21 | }) as AuthorizerConfig; 22 | -------------------------------------------------------------------------------- /src/state/globalContext.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue'; 2 | import { Authorizer } from '@authorizerdev/authorizer-js'; 3 | import type { AuthorizerContextPropsType } from '../types'; 4 | import { hasWindow } from '../utils/window'; 5 | export default reactive({ 6 | user: null, 7 | token: null, 8 | loading: false, 9 | setLoading: () => undefined, 10 | setToken: () => undefined, 11 | setUser: () => undefined, 12 | setAuthData: () => undefined, 13 | authorizerRef: new Authorizer({ 14 | authorizerURL: `http://localhost:8080`, 15 | redirectURL: hasWindow() ? window.location.origin : '/', 16 | clientID: '' 17 | }), 18 | logout: async () => undefined 19 | }) as AuthorizerContextPropsType; 20 | -------------------------------------------------------------------------------- /src/styledComponents/StyledButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 40 | 41 | 44 | -------------------------------------------------------------------------------- /src/styledComponents/StyledFlex.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 49 | -------------------------------------------------------------------------------- /src/styledComponents/StyledFooter.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/styledComponents/StyledLink.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 28 | 29 | 32 | -------------------------------------------------------------------------------- /src/styledComponents/StyledMessageWrapper.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 32 | 33 | 36 | -------------------------------------------------------------------------------- /src/styledComponents/StyledPasswordStrength.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 29 | 30 | 33 | -------------------------------------------------------------------------------- /src/styledComponents/StyledPasswordStrengthWrapper.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/styledComponents/StyledSeparator.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/styledComponents/StyledWrapper.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/styledComponents/index.ts: -------------------------------------------------------------------------------- 1 | import StyledWrapper from './StyledWrapper.vue'; 2 | import StyledButton from './StyledButton.vue'; 3 | import StyledLink from './StyledLink.vue'; 4 | import StyledSeparator from './StyledSeparator.vue'; 5 | import StyledFooter from './StyledFooter.vue'; 6 | import StyledMessageWrapper from './StyledMessageWrapper.vue'; 7 | import StyledFlex from './StyledFlex.vue'; 8 | import StyledPasswordStrength from './StyledPasswordStrength.vue'; 9 | import StyledPasswordStrengthWrapper from './StyledPasswordStrengthWrapper.vue'; 10 | 11 | export { 12 | StyledWrapper, 13 | StyledButton, 14 | StyledLink, 15 | StyledSeparator, 16 | StyledFooter, 17 | StyledMessageWrapper, 18 | StyledFlex, 19 | StyledPasswordStrength, 20 | StyledPasswordStrengthWrapper 21 | }; 22 | -------------------------------------------------------------------------------- /src/styles/default.css: -------------------------------------------------------------------------------- 1 | * { 2 | --authorizer-primary-color: #3b82f6; 3 | --authorizer-primary-disabled-color: #60a5fa; 4 | --authorizer-gray-color: #d1d5db; 5 | --authorizer-white-color: #ffffff; 6 | --authorizer-danger-color: #dc2626; 7 | --authorizer-success-color: #10b981; 8 | --authorizer-text-color: #374151; 9 | --authorizer-fonts-font-stack: -apple-system, system-ui, sans-serif; 10 | --authorizer-fonts-large-text: 18px; 11 | --authorizer-fonts-medium-text: 14px; 12 | --authorizer-fonts-small-text: 12px; 13 | --authorizer-fonts-tiny-text: 10px; 14 | --authorizer-radius-card: 5px; 15 | --authorizer-radius-button: 5px; 16 | --authorizer-radius-input: 5px; 17 | } 18 | .styled-form-group { 19 | width: 100%; 20 | border: 0px; 21 | background-color: var(--authorizer-white-color); 22 | padding: 0 0 15px; 23 | } 24 | .form-input-label { 25 | padding: 2.5px; 26 | } 27 | .form-input-label > span { 28 | color: var(--authorizer-danger-color); 29 | } 30 | .form-input-field { 31 | width: 100%; 32 | margin-top: 5px; 33 | padding: 10px; 34 | display: flex; 35 | flex-direction: column; 36 | align-items: center; 37 | border-radius: var(--authorizer-radius-input); 38 | border: 1px; 39 | border-style: solid; 40 | border-color: var(--authorizer-text-color); 41 | } 42 | .input-error-content { 43 | border-color: var(--authorizer-danger-color) !important; 44 | } 45 | .input-error-content:hover { 46 | outline-color: var(--authorizer-danger-color); 47 | } 48 | .input-error-content:focus { 49 | outline-color: var(--authorizer-danger-color); 50 | } 51 | .form-input-error { 52 | font-size: 12px; 53 | font-weight: 400; 54 | color: red; 55 | border-color: var(--authorizer-danger-color); 56 | } 57 | .styled-check-box-label { 58 | margin-left: 5px; 59 | } 60 | .styled-button { 61 | padding: 15px 10px !important; 62 | display: flex; 63 | justify-content: center; 64 | align-items: center; 65 | min-width: 375px; 66 | max-height: 64px; 67 | border-radius: var(--authorizer-radius-button); 68 | border-color: var(--authorizer-text-color) !important; 69 | border-style: solid !important; 70 | cursor: pointer; 71 | position: relative; 72 | } 73 | .styled-button:disabled { 74 | cursor: not-allowed; 75 | background-color: var(--authorizer-primary-disabled-color); 76 | } 77 | .styled-footer { 78 | display: flex; 79 | flex-direction: column; 80 | justify-content: center; 81 | align-items: center; 82 | margin-top: 15px; 83 | } 84 | .styled-link { 85 | color: var(--authorizer-primary-color); 86 | cursor: pointer; 87 | } 88 | .styled-message-wrapper { 89 | padding: 10px; 90 | color: white; 91 | border-radius: var(--authorizer-radius-card); 92 | margin: 10px 0px; 93 | font-size: var(--authorizer-fonts-small-text); 94 | } 95 | .styled-password-strength { 96 | width: 100%; 97 | height: 10px; 98 | flex: 0.75; 99 | border-radius: 5px; 100 | margin-right: 5px; 101 | background-color: var(--authorizer-primary-color); 102 | } 103 | .styled-password-strength-wrapper { 104 | margin: 2% 0 0; 105 | } 106 | .styled-separator { 107 | display: flex; 108 | align-items: center; 109 | text-align: center; 110 | margin: 10px 0px; 111 | } 112 | .styled-separator::before { 113 | content: ''; 114 | flex: 1; 115 | border-bottom: 1px solid var(--authorizer-gray-color); 116 | } 117 | .styled-separator::after { 118 | content: ''; 119 | flex: 1; 120 | border-bottom: 1px solid var(--authorizer-gray-color); 121 | } 122 | .styled-separator:not(:empty)::before { 123 | margin-right: 0.25em; 124 | } 125 | .styled-separator:not(:empty)::after { 126 | margin-left: 0.25em; 127 | } 128 | .styled-wrapper { 129 | font-family: var(--authorizer-fonts-font-stack); 130 | color: var(--authorizer-text-color); 131 | font-size: var(--authorizer-fonts-medium-text); 132 | box-sizing: border-box; 133 | width: 100%; 134 | } 135 | .styled-wrapper *, 136 | *:before, 137 | *:after { 138 | box-sizing: inherit; 139 | } 140 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import { type AuthToken, type User, type Authorizer } from '@authorizerdev/authorizer-js'; 2 | import { AuthorizerProviderActionType } from '../constants'; 3 | import type { Ref } from 'vue'; 4 | 5 | export type AuthorizerConfig = { 6 | authorizerURL: string; 7 | redirectURL: string; 8 | client_id: string; 9 | is_google_login_enabled: boolean; 10 | is_github_login_enabled: boolean; 11 | is_facebook_login_enabled: boolean; 12 | is_linkedin_login_enabled: boolean; 13 | is_apple_login_enabled: boolean; 14 | is_twitter_login_enabled: boolean; 15 | is_microsoft_login_enabled: boolean; 16 | is_discord_login_enabled: boolean; 17 | is_roblox_login_enabled: boolean; 18 | is_email_verification_enabled: boolean; 19 | is_basic_authentication_enabled: boolean; 20 | is_magic_link_login_enabled: boolean; 21 | is_sign_up_enabled: boolean; 22 | is_strong_password_enabled: boolean; 23 | }; 24 | 25 | export type AuthorizerConfigInput = { 26 | authorizerURL: string; 27 | redirectURL?: string; 28 | clientID?: string; 29 | }; 30 | 31 | export type AuthorizerState = { 32 | user: User | null; 33 | token: AuthToken | null; 34 | loading: boolean; 35 | config: AuthorizerConfig; 36 | }; 37 | 38 | export type AuthorizerProviderAction = { 39 | type: AuthorizerProviderActionType; 40 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 41 | payload: any; 42 | }; 43 | 44 | export type AuthorizerContextPropsType = { 45 | user: null | User; 46 | token: null | AuthToken; 47 | loading: boolean; 48 | logout: () => Promise; 49 | setLoading: (data: boolean) => void; 50 | setUser: (data: null | User) => void; 51 | setToken: (data: null | AuthToken) => void; 52 | setAuthData: (data: AuthorizerState) => void; 53 | authorizerRef: Authorizer; 54 | }; 55 | 56 | export type AuthorizerContextOutputType = { 57 | user: Ref; 58 | token: Ref; 59 | loading: Ref; 60 | logout: Ref<() => Promise>; 61 | setLoading: Ref<(data: boolean) => void>; 62 | setUser: Ref<(data: null | User) => void>; 63 | setToken: Ref<(data: null | AuthToken) => void>; 64 | setAuthData: Ref<(data: AuthorizerState) => void>; 65 | authorizerRef: Ref; 66 | config?: { 67 | authorizerURL: Ref; 68 | redirectURL: Ref; 69 | client_id: Ref; 70 | is_google_login_enabled: Ref; 71 | is_github_login_enabled: Ref; 72 | is_facebook_login_enabled: Ref; 73 | is_linkedin_login_enabled: Ref; 74 | is_apple_login_enabled: Ref; 75 | is_twitter_login_enabled: Ref; 76 | is_microsoft_login_enabled: Ref; 77 | is_discord_login_enabled: Ref; 78 | is_roblox_login_enabled: Ref; 79 | is_email_verification_enabled: Ref; 80 | is_basic_authentication_enabled: Ref; 81 | is_magic_link_login_enabled: Ref; 82 | is_sign_up_enabled: Ref; 83 | is_strong_password_enabled: Ref; 84 | }; 85 | }; 86 | 87 | export type OtpDataType = { 88 | isScreenVisible: boolean; 89 | email: string; 90 | }; 91 | 92 | export type URLPropsType = { 93 | redirectURL?: string; 94 | scope?: string[]; 95 | state?: string; 96 | redirect_uri?: string; 97 | roles?: string[]; 98 | }; 99 | -------------------------------------------------------------------------------- /src/utils/common.ts: -------------------------------------------------------------------------------- 1 | import { hasWindow } from './window'; 2 | 3 | export const getIntervalDiff = (accessTokenExpiresAt: number) => { 4 | const expiresAt = accessTokenExpiresAt * 1000 - 300000; 5 | const currentDate = new Date(); 6 | 7 | const millisecond = new Date(expiresAt).getTime() - currentDate.getTime(); 8 | return millisecond; 9 | }; 10 | 11 | export const getCrypto = () => { 12 | //ie 11.x uses msCrypto 13 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 14 | // @ts-expect-error 15 | return hasWindow() ? window.crypto || window.msCrypto : null; 16 | }; 17 | 18 | export const createRandomString = () => { 19 | const charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.'; 20 | let random = ''; 21 | const crypto = getCrypto(); 22 | if (crypto) { 23 | const randomValues = Array.from(crypto.getRandomValues(new Uint8Array(43))); 24 | randomValues.forEach((v: number) => (random += charset[v % charset.length])); 25 | } 26 | return random; 27 | }; 28 | 29 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 30 | export const createQueryParams = (params: any) => { 31 | return Object.keys(params) 32 | .filter((k) => typeof params[k] !== 'undefined') 33 | .map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(params[k])) 34 | .join('&'); 35 | }; 36 | 37 | export const isValidEmail = (email: string) => { 38 | return String(email) 39 | .toLowerCase() 40 | .match( 41 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 42 | ); 43 | }; 44 | 45 | export const capitalizeFirstLetter = (data: string) => { 46 | return data ? data.charAt(0).toUpperCase() + data.slice(1) : null; 47 | }; 48 | 49 | export const isValidOtp = (otp: string) => { 50 | const re = /^([A-Z0-9]{6})$/; 51 | return otp && re.test(String(otp.trim())); 52 | }; 53 | 54 | export const formatErrorMessage = (message: string) => { 55 | return message.replace(`[GraphQL] `, ''); 56 | }; 57 | 58 | export const hasSpecialChar = (char: string) => { 59 | const re = /[`!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?~]/; 60 | return re.test(char); 61 | }; 62 | 63 | export const validatePassword = (value = '') => { 64 | const res: { 65 | strength: string; 66 | score: number; 67 | hasSixChar: boolean; 68 | hasLowerCase: boolean; 69 | hasNumericChar: boolean; 70 | hasSpecialChar: boolean; 71 | hasUpperCase: boolean; 72 | maxThirtySixChar: boolean; 73 | } = { 74 | score: 0, 75 | strength: '', 76 | hasSixChar: false, 77 | hasLowerCase: false, 78 | hasUpperCase: false, 79 | hasNumericChar: false, 80 | hasSpecialChar: false, 81 | maxThirtySixChar: false 82 | }; 83 | 84 | if (value.length >= 6) { 85 | res.score = res.score + 1; 86 | res.hasSixChar = true; 87 | } 88 | 89 | if (value.length > 0 && value.length <= 36) { 90 | res.score = res.score + 1; 91 | res.maxThirtySixChar = true; 92 | } 93 | 94 | Array.from(value).forEach((char) => { 95 | if (char >= 'A' && char <= 'Z' && !res.hasUpperCase) { 96 | res.score = res.score + 1; 97 | res.hasUpperCase = true; 98 | } else if (char >= 'a' && char <= 'z' && !res.hasLowerCase) { 99 | res.score = res.score + 1; 100 | res.hasLowerCase = true; 101 | } else if (char >= '0' && char <= '9' && !res.hasNumericChar) { 102 | res.score = res.score + 1; 103 | res.hasNumericChar = true; 104 | } else if (hasSpecialChar(char) && !res.hasSpecialChar) { 105 | res.score = res.score + 1; 106 | res.hasSpecialChar = true; 107 | } 108 | }); 109 | 110 | if (res.score <= 2) { 111 | res.strength = 'Weak'; 112 | } else if (res.score <= 4) { 113 | res.strength = 'Good'; 114 | } else if (res.score <= 5) { 115 | res.strength = 'Strong'; 116 | } else { 117 | res.strength = 'Very Strong'; 118 | } 119 | 120 | const isValid = Object.values(res).every((i) => Boolean(i)); 121 | return { ...res, isValid }; 122 | }; 123 | -------------------------------------------------------------------------------- /src/utils/url.ts: -------------------------------------------------------------------------------- 1 | import { hasWindow } from './window'; 2 | 3 | export const getSearchParams = (search = '') => { 4 | let searchPrams = search; 5 | if (!searchPrams && hasWindow()) { 6 | searchPrams = window.location.search; 7 | } 8 | const urlSearchParams = new URLSearchParams(`${searchPrams}`); 9 | const params = Object.fromEntries(urlSearchParams.entries()); 10 | return params; 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/window.ts: -------------------------------------------------------------------------------- 1 | export const hasWindow = () => typeof window !== 'undefined'; 2 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | // tsconfig.app.json 2 | { 3 | "extends": ["@vue/tsconfig/tsconfig.json", "@vue/tsconfig/tsconfig.dom.json"], 4 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "example/**/*", "example/**/*.vue"], 5 | "exclude": ["src/**/__tests__/*", "src/**/*.cy.ts"], 6 | "compilerOptions": { 7 | "composite": true, 8 | "baseUrl": ".", 9 | "noEmit": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.build-types.json: -------------------------------------------------------------------------------- 1 | // tsconfig.build-types.json 2 | { 3 | "include": ["src/**/*"], 4 | "exclude": ["node_modules", "**/*.cy.ts", "**/*.spec.ts", "**/__tests__/**/*"] 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.config.json: -------------------------------------------------------------------------------- 1 | // tsconfig.config.json 2 | { 3 | "extends": ["@tsconfig/node18/tsconfig.json", "@vue/tsconfig/tsconfig.json"], 4 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "types": ["node"], 8 | "noEmit": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // tsconfig.json 2 | { 3 | "files": [], 4 | "references": [{ "path": "./tsconfig.config.json" }, { "path": "./tsconfig.app.json" }] 5 | } 6 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path'; 2 | import { defineConfig } from 'vite'; 3 | import Vue from '@vitejs/plugin-vue'; 4 | 5 | export default defineConfig({ 6 | // If our .vue files have a style, it will be compiled as a single `.css` file under /dist. 7 | plugins: [Vue()], 8 | 9 | build: { 10 | // Output compiled files to /dist. 11 | outDir: './dist', 12 | lib: { 13 | // Set the entry point (file that contains our components exported). 14 | entry: resolve(__dirname, 'src/index.ts'), 15 | // Name of the library. 16 | name: 'authorizer-vue', 17 | // We are building for CJS and ESM, use a function to rename automatically files. 18 | // Example: my-component-library.esm.js 19 | fileName: (format) => `${'@authorizerdev/authorizer-vue'}.${format}.js` 20 | }, 21 | rollupOptions: { 22 | // Vue is provided by the parent project, don't compile Vue source-code inside our library. 23 | external: ['vue', '@authorizerdev/authorizer-js'], 24 | output: { 25 | globals: { 26 | vue: 'Vue', 27 | '@authorizerdev/authorizer-js': 'authorizer-js' 28 | } 29 | }, 30 | watch: { 31 | include: './src/**', 32 | clearScreen: false 33 | } 34 | } 35 | } 36 | }); 37 | --------------------------------------------------------------------------------