aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/auth.server.ts
blob: f762cef5b255f95f838bfc723583cd1ee994c874 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import { env as env_priv } from '$env/dynamic/private';
import { env } from '$env/dynamic/public';
import * as client from 'openid-client';
import oncePromise from './oncePromise';
import type { Cookies } from '@sveltejs/kit';

const server = new URL(env.PUBLIC_AUTH_KEYCLOAK_ISSUER);
const clientId = env_priv.PRIVATE_AUTH_KEYCLOAK_ID;
const clientSecret = env_priv.PRIVATE_AUTH_KEYCLOAK_SECRET;
const redirectPath = '/login/callback';

// Only trigger discovery on first client.discovery (resetting the function after a failed discovery)
export const getConfig = oncePromise(() =>
  client.discovery(server, clientId, clientSecret).then((config) => {
    client.useJwtResponseMode(config);
    return config;
  })
);
const codeVerifier = client.randomPKCECodeVerifier();

export const getAuthorizeUrl = async (
  currentUrl: URL | string,
  scope: string[]
) => {
  if (!scope.includes('openid')) scope.unshift('openid');
  // do same for `email` maybe?

  const config = await getConfig();
  const redirectUri = new URL(redirectPath, currentUrl);
  const codeChallenge = await client.calculatePKCECodeChallenge(codeVerifier);
  const codeChallengeMethod = 'S256';
  let nonce: string | undefined = undefined;

  // redirect user to as.authorization_endpoint
  let parameters: Record<string, string> = {
    redirect_uri: redirectUri.href,
    scope: scope.join(' '),
    code_challenge: codeChallenge,
    code_challenge_method: codeChallengeMethod,
  };

  /**
   * We cannot be sure the AS supports PKCE so we're going to use nonce too. Use
   * of PKCE is backwards compatible even if the AS doesn't support it which is
   * why we're using it regardless.
   */
  if (!config.serverMetadata().supportsPKCE()) {
    nonce = client.randomNonce();
    parameters.nonce = nonce;
  }

  const redirectTo = client.buildAuthorizationUrl(config, parameters);

  return {
    /** Defined if PKCE isnt supported */
    nonce,
    /** Redirect Target URL */
    redirectTo,
    /** Where we get the user back on */
    returnURI: redirectUri,
  };
};
/** Throws on failure */
export const authorizeNewSession = async (
  currentUrl: URL,
  nonce: string | undefined
) => {
  const config = await getConfig();

  let tokens = await client.authorizationCodeGrant(config, currentUrl, {
    pkceCodeVerifier: codeVerifier,
    expectedNonce: nonce,
    idTokenExpected: true,
  });

  return tokens;
};

export const unsetCookies = (cookies: Cookies) => {
  for (const v of [
    'oid__access_token',
    'oid__refresh_token',
    'oid__token_type',
    'oid__expires_at',
    'oid__scopes',
  ])
    if (cookies.get(v)) cookies.delete(v, { path: '/' });
};
export const setCookies = (
  cookies: Cookies,
  tokens: client.TokenEndpointResponse & client.TokenEndpointResponseHelpers
) => {
  for (const [k, v] of Object.entries({
    oid__access_token: tokens.access_token,
    oid__refresh_token: tokens.refresh_token,
    oid__token_type: tokens.token_type,
    oid__expires_at: '' + (Date.now() + (tokens.expiresIn() ?? 0) * 1000),
    oid__scopes: tokens.scope,
  }))
    if (v)
      cookies.set(k, v, {
        path: '/',
        secure: true,
        httpOnly: true,
        sameSite: true,
      });
};