Merge branch 'main' into feature/discord-provider

This commit is contained in:
Alexander Staroselsky 2022-03-27 09:22:45 -06:00
commit 9a8ffa9397
15 changed files with 2942 additions and 2803 deletions

View File

@ -1,3 +1,4 @@
README.md README.md
app/ app/
*.cjs *.cjs
dist/

2
.gitignore vendored
View File

@ -76,4 +76,4 @@ typings/
.fusebox/ .fusebox/
# Build output # Build output
dist/* dist/

View File

@ -144,6 +144,10 @@ SvelteKitAuth is inspired by the [NextAuth.js](https://next-auth.js.org/) packag
As it leverages classes and Typescript, the implementation of such providers is very straightforward, and in the future it will even be possible to register multiple SvelteKitAuth handlers in the same project, should the need arise, by leveraging a class-based client and server setup. As it leverages classes and Typescript, the implementation of such providers is very straightforward, and in the future it will even be possible to register multiple SvelteKitAuth handlers in the same project, should the need arise, by leveraging a class-based client and server setup.
## Examples
Looking for help? Check out the [example app](./app/) in the repository source. Make something cool you want to show off? Share it with others [in the discussion section](https://github.com/Dan6erbond/sk-auth/discussions/72).
## Contributing ## Contributing
🚧 Work in Progress! 🚧 Work in Progress!

View File

@ -1,2 +1,6 @@
node_modules/
README.md README.md
*.cjs *.cjs
.svelte-kit/
static/
build/

View File

@ -9,7 +9,7 @@
"format": "prettier --write --plugin-search-dir=. ." "format": "prettier --write --plugin-search-dir=. ."
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/kit": "next", "@sveltejs/kit": "^1.0.0-next.259",
"@types/prismjs": "^1.16.5", "@types/prismjs": "^1.16.5",
"@typescript-eslint/eslint-plugin": "^4.19.0", "@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0", "@typescript-eslint/parser": "^4.19.0",

View File

@ -1,13 +1,15 @@
import type { Handle } from "@sveltejs/kit"; import type { Handle } from "@sveltejs/kit";
import { appAuth } from "$lib/appAuth"; import { appAuth } from "$lib/appAuth";
export const handle: Handle = async ({ request, render }) => { export const handle: Handle = async ({ event, resolve }) => {
// TODO https://github.com/sveltejs/kit/issues/1046 // TODO https://github.com/sveltejs/kit/issues/1046
if (request.query.has("_method")) {
request.method = request.query.get("_method").toUpperCase();
if (event.request.query.has("_method")) {
event.request.method = event.request.query.get("_method").toUpperCase();
} }
const response = await render(request); const response = await resolve(event);
return response; return response;
}; };

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "sk-auth", "name": "sk-auth",
"version": "0.4.0", "version": "0.4.1",
"description": "Authentication library for use with SvelteKit featuring built-in OAuth providers and zero restriction customization!", "description": "Authentication library for use with SvelteKit featuring built-in OAuth providers and zero restriction customization!",
"main": "dist/index.js", "main": "dist/index.js",
"module": "dist/index.esm.js", "module": "dist/index.esm.js",
@ -18,6 +18,7 @@
"README.md" "README.md"
], ],
"scripts": { "scripts": {
"prepare": "npm run build",
"build": "rollup --config", "build": "rollup --config",
"dev": "rollup --config --watch", "dev": "rollup --config --watch",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
@ -47,7 +48,7 @@
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-typescript": "^8.2.1", "@rollup/plugin-typescript": "^8.2.1",
"@sveltejs/kit": "^1.0.0-next.211", "@sveltejs/kit": "^1.0.0-next.259",
"@types/jsonwebtoken": "^8.5.1", "@types/jsonwebtoken": "^8.5.1",
"@typescript-eslint/eslint-plugin": "^4.23.0", "@typescript-eslint/eslint-plugin": "^4.23.0",
"@typescript-eslint/parser": "^4.23.0", "@typescript-eslint/parser": "^4.23.0",

View File

@ -1,7 +1,6 @@
import type { GetSession, RequestHandler } from "@sveltejs/kit"; import type { GetSession, RequestHandler } from "@sveltejs/kit";
import type { EndpointOutput } from "@sveltejs/kit/types/endpoint"; import type { EndpointOutput } from "@sveltejs/kit/types/endpoint";
import { RequestHeaders } from "@sveltejs/kit/types/helper"; import { RequestEvent } from "@sveltejs/kit/types/hooks";
import { ServerRequest } from "@sveltejs/kit/types/hooks";
import cookie from "cookie"; import cookie from "cookie";
import * as jsonwebtoken from "jsonwebtoken"; import * as jsonwebtoken from "jsonwebtoken";
import type { JWT, Session } from "./interfaces"; import type { JWT, Session } from "./interfaces";
@ -45,12 +44,12 @@ export class Auth {
return "svelte_auth_secret"; return "svelte_auth_secret";
} }
async getToken(headers: RequestHeaders) { async getToken(headers: any) {
if (!headers.cookie) { if (!headers.get("cookie")) {
return null; return null;
} }
const cookies = cookie.parse(headers.cookie); const cookies = cookie.parse(headers.get("cookie"));
if (!cookies.svelteauthjwt) { if (!cookies.svelteauthjwt) {
return null; return null;
@ -71,7 +70,7 @@ export class Auth {
} }
getBaseUrl(host?: string) { getBaseUrl(host?: string) {
const protocol = this.config?.protocol ?? "http"; const protocol = this.config?.protocol ?? "https";
host = this.config?.host ?? host; host = this.config?.host ?? host;
return `${protocol}://${host}`; return `${protocol}://${host}`;
} }
@ -86,7 +85,7 @@ export class Auth {
return new URL(pathname, this.getBaseUrl(host)).href; return new URL(pathname, this.getBaseUrl(host)).href;
} }
setToken(headers: RequestHeaders, newToken: JWT | any) { setToken(headers: any, newToken: JWT | any) {
const originalToken = this.getToken(headers); const originalToken = this.getToken(headers);
return { return {
@ -113,12 +112,10 @@ export class Auth {
return redirect; return redirect;
} }
async handleProviderCallback( async handleProviderCallback(event: RequestEvent, provider: Provider): Promise<EndpointOutput> {
request: ServerRequest, const { headers } = event.request;
provider: Provider, const { url } = event;
): Promise<EndpointOutput> { const [profile, redirectUrl] = await provider.callback(event, this);
const { headers, url } = request;
const [profile, redirectUrl] = await provider.callback(request, this);
let token = (await this.getToken(headers)) ?? { user: {} }; let token = (await this.getToken(headers)) ?? { user: {} };
if (this.config?.callbacks?.jwt) { if (this.config?.callbacks?.jwt) {
@ -139,11 +136,12 @@ export class Auth {
}; };
} }
async handleEndpoint(request: ServerRequest): Promise<EndpointOutput> { async handleEndpoint(event: RequestEvent): Promise<EndpointOutput> {
const { headers, method, url } = request; const { headers, method } = event.request;
const { url } = event;
if (url.pathname === this.getPath("signout")) { if (url.pathname === this.getPath("signout")) {
const token = this.setToken(headers, {}); const token = this.setToken(event.request.headers, {});
const jwt = this.signToken(token); const jwt = this.signToken(token);
if (method === "POST") { if (method === "POST") {
@ -177,9 +175,9 @@ export class Auth {
); );
if (provider) { if (provider) {
if (match.groups.method === "signin") { if (match.groups.method === "signin") {
return await provider.signin(request, this); return await provider.signin(event, this);
} else { } else {
return await this.handleProviderCallback(request, provider); return await this.handleProviderCallback(event, provider);
} }
} }
} }
@ -190,13 +188,13 @@ export class Auth {
}; };
} }
get: RequestHandler = async (request) => { get: RequestHandler = async (event: RequestEvent): Promise<any> => {
const { url } = request; const { url } = event;
if (url.pathname === this.getPath("csrf")) { if (url.pathname === this.getPath("csrf")) {
return { body: "1234" }; // TODO: Generate real token return { body: "1234" }; // TODO: Generate real token
} else if (url.pathname === this.getPath("session")) { } else if (url.pathname === this.getPath("session")) {
const session = await this.getSession(request); const session = await this.getSession(event);
return { return {
body: { body: {
session, session,
@ -204,15 +202,16 @@ export class Auth {
}; };
} }
return await this.handleEndpoint(request); return await this.handleEndpoint(event);
}; };
post: RequestHandler = async (request) => { post: RequestHandler = async (event: RequestEvent) => {
return await this.handleEndpoint(request); return await this.handleEndpoint(event);
}; };
getSession: GetSession = async ({ headers }) => { getSession: GetSession = async (event: RequestEvent) => {
const token = await this.getToken(headers); const { request } = event;
const token = await this.getToken(request.headers);
if (token) { if (token) {
if (this.config?.callbacks?.session) { if (this.config?.callbacks?.session) {

View File

@ -1,5 +1,5 @@
import type { EndpointOutput } from "@sveltejs/kit"; import type { EndpointOutput } from "@sveltejs/kit";
import { ServerRequest } from "@sveltejs/kit/types/hooks"; import { RequestEvent } from "@sveltejs/kit/types/hooks";
import type { Auth } from "../auth"; import type { Auth } from "../auth";
import type { CallbackResult } from "../types"; import type { CallbackResult } from "../types";
@ -28,12 +28,12 @@ export abstract class Provider<T extends ProviderConfig = ProviderConfig> {
} }
abstract signin<Locals extends Record<string, any> = Record<string, any>, Body = unknown>( abstract signin<Locals extends Record<string, any> = Record<string, any>, Body = unknown>(
request: ServerRequest<Locals, Body>, event: RequestEvent,
svelteKitAuth: Auth, svelteKitAuth: Auth,
): EndpointOutput | Promise<EndpointOutput>; ): EndpointOutput | Promise<EndpointOutput>;
abstract callback<Locals extends Record<string, any> = Record<string, any>, Body = unknown>( abstract callback<Locals extends Record<string, any> = Record<string, any>, Body = unknown>(
request: ServerRequest<Locals, Body>, event: RequestEvent,
svelteKitAuth: Auth, svelteKitAuth: Auth,
): CallbackResult | Promise<CallbackResult>; ): CallbackResult | Promise<CallbackResult>;
} }

View File

@ -1,5 +1,5 @@
import type { EndpointOutput } from "@sveltejs/kit/types/endpoint"; import type { EndpointOutput } from "@sveltejs/kit/types/endpoint";
import { ServerRequest } from "@sveltejs/kit/types/hooks"; import { RequestEvent } from "@sveltejs/kit/types/hooks";
import type { Auth } from "../auth"; import type { Auth } from "../auth";
import type { CallbackResult } from "../types"; import type { CallbackResult } from "../types";
import { Provider, ProviderConfig } from "./base"; import { Provider, ProviderConfig } from "./base";
@ -25,7 +25,7 @@ export abstract class OAuth2BaseProvider<
T extends OAuth2BaseProviderConfig, T extends OAuth2BaseProviderConfig,
> extends Provider<T> { > extends Provider<T> {
abstract getAuthorizationUrl( abstract getAuthorizationUrl(
request: ServerRequest, event: RequestEvent,
auth: Auth, auth: Auth,
state: string, state: string,
nonce: string, nonce: string,
@ -33,14 +33,15 @@ export abstract class OAuth2BaseProvider<
abstract getTokens(code: string, redirectUri: string): TokensType | Promise<TokensType>; abstract getTokens(code: string, redirectUri: string): TokensType | Promise<TokensType>;
abstract getUserProfile(tokens: any): ProfileType | Promise<ProfileType>; abstract getUserProfile(tokens: any): ProfileType | Promise<ProfileType>;
async signin(request: ServerRequest, auth: Auth): Promise<EndpointOutput> { async signin(event: RequestEvent, auth: Auth): Promise<EndpointOutput> {
const { method, url } = request; const { method } = event.request;
const { url } = event;
const state = [ const state = [
`redirect=${url.searchParams.get("redirect") ?? this.getUri(auth, "/", url.host)}`, `redirect=${url.searchParams.get("redirect") ?? this.getUri(auth, "/", url.host)}`,
].join(","); ].join(",");
const base64State = Buffer.from(state).toString("base64"); const base64State = Buffer.from(state).toString("base64");
const nonce = Math.round(Math.random() * 1000).toString(); // TODO: Generate random based on user values const nonce = Math.round(Math.random() * 1000).toString(); // TODO: Generate random based on user values
const authUrl = await this.getAuthorizationUrl(request, auth, base64State, nonce); const authUrl = await this.getAuthorizationUrl(event, auth, base64State, nonce);
if (method === "POST") { if (method === "POST") {
return { return {
@ -68,7 +69,8 @@ export abstract class OAuth2BaseProvider<
} }
} }
async callback({ url }: ServerRequest, auth: Auth): Promise<CallbackResult> { async callback(event: RequestEvent, auth: Auth): Promise<any> {
const { request, url } = event;
const code = url.searchParams.get("code"); const code = url.searchParams.get("code");
const redirect = this.getStateValue(url.searchParams, "redirect"); const redirect = this.getStateValue(url.searchParams, "redirect");

View File

@ -1,4 +1,4 @@
import { ServerRequest } from "@sveltejs/kit/types/hooks"; import { RequestEvent } from "@sveltejs/kit/types/hooks";
import type { Auth } from "../auth"; import type { Auth } from "../auth";
import { ucFirst } from "../helpers"; import { ucFirst } from "../helpers";
import { OAuth2BaseProvider, OAuth2BaseProviderConfig, OAuth2Tokens } from "./oauth2.base"; import { OAuth2BaseProvider, OAuth2BaseProviderConfig, OAuth2Tokens } from "./oauth2.base";
@ -37,7 +37,7 @@ export class OAuth2Provider<
}); });
} }
getAuthorizationUrl({ url }: ServerRequest, auth: Auth, state: string, nonce: string) { getAuthorizationUrl({ url }: RequestEvent, auth: Auth, state: string, nonce: string) {
const data = { const data = {
state, state,
nonce, nonce,

View File

@ -1,5 +1,4 @@
import { OAuth2Provider, OAuth2ProviderConfig } from "./oauth2"; import { OAuth2Provider, OAuth2ProviderConfig } from "./oauth2";
import type { ProfileCallback } from "./oauth2.base";
export interface RedditProfile { export interface RedditProfile {
is_employee: boolean; is_employee: boolean;

View File

@ -1,6 +1,5 @@
import { ServerRequest } from "@sveltejs/kit/types/hooks"; import { RequestEvent } from "@sveltejs/kit/types/hooks";
import type { Auth } from "../auth"; import type { Auth } from "../auth";
import type { CallbackResult } from "../types";
import { OAuth2BaseProvider, OAuth2BaseProviderConfig } from "./oauth2.base"; import { OAuth2BaseProvider, OAuth2BaseProviderConfig } from "./oauth2.base";
interface TwitterAuthProviderConfig extends OAuth2BaseProviderConfig { interface TwitterAuthProviderConfig extends OAuth2BaseProviderConfig {
@ -38,7 +37,7 @@ export class TwitterAuthProvider extends OAuth2BaseProvider<any, any, TwitterAut
}; };
} }
async getAuthorizationUrl({ url }: ServerRequest, auth: Auth, state: string, nonce: string) { async getAuthorizationUrl({ url }: RequestEvent, auth: Auth, state: string, nonce: string) {
const endpoint = "https://api.twitter.com/oauth/authorize"; const endpoint = "https://api.twitter.com/oauth/authorize";
const { oauthToken } = await this.getRequestToken(auth, url.host); const { oauthToken } = await this.getRequestToken(auth, url.host);
@ -71,7 +70,8 @@ export class TwitterAuthProvider extends OAuth2BaseProvider<any, any, TwitterAut
return await res.json(); return await res.json();
} }
async callback({ url }: ServerRequest, auth: Auth): Promise<CallbackResult> { async callback(event: RequestEvent, auth: Auth): Promise<any> {
const { url } = event;
const oauthToken = url.searchParams.get("oauth_token"); const oauthToken = url.searchParams.get("oauth_token");
const oauthVerifier = url.searchParams.get("oauth_verifier"); const oauthVerifier = url.searchParams.get("oauth_verifier");
const redirect = this.getStateValue(url.searchParams, "redirect"); const redirect = this.getStateValue(url.searchParams, "redirect");

2331
yarn.lock

File diff suppressed because it is too large Load Diff