@meridianjs/google-oauth 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ import { ModuleDefinition } from '@meridianjs/types';
2
+
3
+ interface GoogleOAuthOptions {
4
+ clientId: string;
5
+ clientSecret: string;
6
+ callbackUrl: string;
7
+ frontendUrl: string;
8
+ }
9
+ interface GoogleProfile {
10
+ googleId: string;
11
+ email: string;
12
+ firstName: string | null;
13
+ lastName: string | null;
14
+ picture: string | null;
15
+ }
16
+ declare class GoogleOAuthService {
17
+ private readonly options;
18
+ private readonly jwks;
19
+ constructor(container: any);
20
+ /** Build the Google OAuth authorization URL with the given state parameter. */
21
+ getAuthUrl(state: string): string;
22
+ /**
23
+ * Exchange an authorization code for a GoogleProfile.
24
+ * Verifies the id_token signature against Google's JWK keyset per
25
+ * https://developers.google.com/identity/openid-connect/openid-connect#validatinganidtoken
26
+ */
27
+ exchangeCode(code: string): Promise<GoogleProfile>;
28
+ get frontendUrl(): string;
29
+ }
30
+
31
+ declare const GoogleOAuthModule: ModuleDefinition;
32
+
33
+ export { type GoogleOAuthOptions, GoogleOAuthService, type GoogleProfile, GoogleOAuthModule as default };
@@ -0,0 +1,33 @@
1
+ import { ModuleDefinition } from '@meridianjs/types';
2
+
3
+ interface GoogleOAuthOptions {
4
+ clientId: string;
5
+ clientSecret: string;
6
+ callbackUrl: string;
7
+ frontendUrl: string;
8
+ }
9
+ interface GoogleProfile {
10
+ googleId: string;
11
+ email: string;
12
+ firstName: string | null;
13
+ lastName: string | null;
14
+ picture: string | null;
15
+ }
16
+ declare class GoogleOAuthService {
17
+ private readonly options;
18
+ private readonly jwks;
19
+ constructor(container: any);
20
+ /** Build the Google OAuth authorization URL with the given state parameter. */
21
+ getAuthUrl(state: string): string;
22
+ /**
23
+ * Exchange an authorization code for a GoogleProfile.
24
+ * Verifies the id_token signature against Google's JWK keyset per
25
+ * https://developers.google.com/identity/openid-connect/openid-connect#validatinganidtoken
26
+ */
27
+ exchangeCode(code: string): Promise<GoogleProfile>;
28
+ get frontendUrl(): string;
29
+ }
30
+
31
+ declare const GoogleOAuthModule: ModuleDefinition;
32
+
33
+ export { type GoogleOAuthOptions, GoogleOAuthService, type GoogleProfile, GoogleOAuthModule as default };
package/dist/index.js ADDED
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ GoogleOAuthService: () => GoogleOAuthService,
24
+ default: () => index_default
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/service.ts
29
+ var import_jose = require("jose");
30
+ var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
31
+ var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
32
+ var GOOGLE_JWKS_URL = "https://www.googleapis.com/oauth2/v3/certs";
33
+ var GOOGLE_ISSUERS = ["https://accounts.google.com", "accounts.google.com"];
34
+ var GoogleOAuthService = class {
35
+ options;
36
+ jwks;
37
+ constructor(container) {
38
+ this.options = container.resolve("moduleOptions");
39
+ try {
40
+ const parsed = new URL(this.options.frontendUrl);
41
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
42
+ throw new Error("must be http or https");
43
+ }
44
+ } catch {
45
+ throw new Error(
46
+ `[google-oauth] Invalid frontendUrl: "${this.options.frontendUrl}". Must be a valid http:// or https:// URL.`
47
+ );
48
+ }
49
+ this.jwks = (0, import_jose.createRemoteJWKSet)(new URL(GOOGLE_JWKS_URL));
50
+ }
51
+ /** Build the Google OAuth authorization URL with the given state parameter. */
52
+ getAuthUrl(state) {
53
+ const params = new URLSearchParams({
54
+ client_id: this.options.clientId,
55
+ redirect_uri: this.options.callbackUrl,
56
+ response_type: "code",
57
+ scope: "openid email profile",
58
+ state,
59
+ access_type: "online"
60
+ });
61
+ return `${GOOGLE_AUTH_URL}?${params.toString()}`;
62
+ }
63
+ /**
64
+ * Exchange an authorization code for a GoogleProfile.
65
+ * Verifies the id_token signature against Google's JWK keyset per
66
+ * https://developers.google.com/identity/openid-connect/openid-connect#validatinganidtoken
67
+ */
68
+ async exchangeCode(code) {
69
+ const body = new URLSearchParams({
70
+ code,
71
+ client_id: this.options.clientId,
72
+ client_secret: this.options.clientSecret,
73
+ redirect_uri: this.options.callbackUrl,
74
+ grant_type: "authorization_code"
75
+ });
76
+ const tokenRes = await fetch(GOOGLE_TOKEN_URL, {
77
+ method: "POST",
78
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
79
+ body: body.toString()
80
+ });
81
+ if (!tokenRes.ok) {
82
+ const text = await tokenRes.text().catch(() => "");
83
+ throw Object.assign(new Error(`Google token exchange failed: ${text}`), { status: 502 });
84
+ }
85
+ const tokenData = await tokenRes.json();
86
+ if (!tokenData.id_token) {
87
+ throw Object.assign(new Error("Google did not return an id_token"), { status: 502 });
88
+ }
89
+ let payload;
90
+ try {
91
+ const { payload: verified } = await (0, import_jose.jwtVerify)(tokenData.id_token, this.jwks, {
92
+ audience: this.options.clientId,
93
+ issuer: GOOGLE_ISSUERS
94
+ });
95
+ payload = verified;
96
+ } catch (err) {
97
+ throw Object.assign(
98
+ new Error(`id_token verification failed: ${err.message ?? "invalid token"}`),
99
+ { status: 502 }
100
+ );
101
+ }
102
+ if (!payload.email_verified) {
103
+ throw Object.assign(new Error("Google email address is not verified"), { status: 403 });
104
+ }
105
+ return {
106
+ googleId: payload.sub,
107
+ email: payload.email,
108
+ firstName: payload.given_name ?? null,
109
+ lastName: payload.family_name ?? null,
110
+ picture: payload.picture ?? null
111
+ };
112
+ }
113
+ get frontendUrl() {
114
+ return this.options.frontendUrl;
115
+ }
116
+ };
117
+
118
+ // src/index.ts
119
+ var GoogleOAuthModule = {
120
+ key: "googleOAuthService",
121
+ service: GoogleOAuthService
122
+ };
123
+ var index_default = GoogleOAuthModule;
124
+ // Annotate the CommonJS export names for ESM import in node:
125
+ 0 && (module.exports = {
126
+ GoogleOAuthService
127
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,100 @@
1
+ // src/service.ts
2
+ import { createRemoteJWKSet, jwtVerify } from "jose";
3
+ var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
4
+ var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
5
+ var GOOGLE_JWKS_URL = "https://www.googleapis.com/oauth2/v3/certs";
6
+ var GOOGLE_ISSUERS = ["https://accounts.google.com", "accounts.google.com"];
7
+ var GoogleOAuthService = class {
8
+ options;
9
+ jwks;
10
+ constructor(container) {
11
+ this.options = container.resolve("moduleOptions");
12
+ try {
13
+ const parsed = new URL(this.options.frontendUrl);
14
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
15
+ throw new Error("must be http or https");
16
+ }
17
+ } catch {
18
+ throw new Error(
19
+ `[google-oauth] Invalid frontendUrl: "${this.options.frontendUrl}". Must be a valid http:// or https:// URL.`
20
+ );
21
+ }
22
+ this.jwks = createRemoteJWKSet(new URL(GOOGLE_JWKS_URL));
23
+ }
24
+ /** Build the Google OAuth authorization URL with the given state parameter. */
25
+ getAuthUrl(state) {
26
+ const params = new URLSearchParams({
27
+ client_id: this.options.clientId,
28
+ redirect_uri: this.options.callbackUrl,
29
+ response_type: "code",
30
+ scope: "openid email profile",
31
+ state,
32
+ access_type: "online"
33
+ });
34
+ return `${GOOGLE_AUTH_URL}?${params.toString()}`;
35
+ }
36
+ /**
37
+ * Exchange an authorization code for a GoogleProfile.
38
+ * Verifies the id_token signature against Google's JWK keyset per
39
+ * https://developers.google.com/identity/openid-connect/openid-connect#validatinganidtoken
40
+ */
41
+ async exchangeCode(code) {
42
+ const body = new URLSearchParams({
43
+ code,
44
+ client_id: this.options.clientId,
45
+ client_secret: this.options.clientSecret,
46
+ redirect_uri: this.options.callbackUrl,
47
+ grant_type: "authorization_code"
48
+ });
49
+ const tokenRes = await fetch(GOOGLE_TOKEN_URL, {
50
+ method: "POST",
51
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
52
+ body: body.toString()
53
+ });
54
+ if (!tokenRes.ok) {
55
+ const text = await tokenRes.text().catch(() => "");
56
+ throw Object.assign(new Error(`Google token exchange failed: ${text}`), { status: 502 });
57
+ }
58
+ const tokenData = await tokenRes.json();
59
+ if (!tokenData.id_token) {
60
+ throw Object.assign(new Error("Google did not return an id_token"), { status: 502 });
61
+ }
62
+ let payload;
63
+ try {
64
+ const { payload: verified } = await jwtVerify(tokenData.id_token, this.jwks, {
65
+ audience: this.options.clientId,
66
+ issuer: GOOGLE_ISSUERS
67
+ });
68
+ payload = verified;
69
+ } catch (err) {
70
+ throw Object.assign(
71
+ new Error(`id_token verification failed: ${err.message ?? "invalid token"}`),
72
+ { status: 502 }
73
+ );
74
+ }
75
+ if (!payload.email_verified) {
76
+ throw Object.assign(new Error("Google email address is not verified"), { status: 403 });
77
+ }
78
+ return {
79
+ googleId: payload.sub,
80
+ email: payload.email,
81
+ firstName: payload.given_name ?? null,
82
+ lastName: payload.family_name ?? null,
83
+ picture: payload.picture ?? null
84
+ };
85
+ }
86
+ get frontendUrl() {
87
+ return this.options.frontendUrl;
88
+ }
89
+ };
90
+
91
+ // src/index.ts
92
+ var GoogleOAuthModule = {
93
+ key: "googleOAuthService",
94
+ service: GoogleOAuthService
95
+ };
96
+ var index_default = GoogleOAuthModule;
97
+ export {
98
+ GoogleOAuthService,
99
+ index_default as default
100
+ };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@meridianjs/google-oauth",
3
+ "version": "0.1.1",
4
+ "description": "Google OAuth 2.0 sign-in module for Meridian",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.mts",
12
+ "default": "./dist/index.mjs"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ }
19
+ },
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
22
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
23
+ "typecheck": "tsc --noEmit",
24
+ "clean": "rm -rf dist",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "dependencies": {
28
+ "@meridianjs/types": "^0.1.0",
29
+ "jose": "^5.9.6"
30
+ },
31
+ "devDependencies": {
32
+ "tsup": "^8.3.5",
33
+ "typescript": "*"
34
+ },
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
41
+ }